18 votos

Deserializar matriz de valores .Propiedades de RED utilizando DataContractJsonSerializer

Estoy trabajando con el DataContractJsonSerializer en Silverlight 4 y quisiera deserializar el siguiente JSON:

{
    "collectionname":"Books",
    "collectionitems": [
            ["12345-67890",201,
             "Book One"],
            ["09876-54321",45,
             "Book Two"]
        ]
}

En las clases como las siguientes:

class BookCollection
{
  public string collectionname { get; set; }
  public List<Book> collectionitems { get; set; }
}

class Book
{
  public string Id { get; set; }
  public int NumberOfPages { get; set; }
  public string Title { get; set; }
}

¿Cuál es el lugar adecuado para extender DataContractJsonSerializer para mapa de el sin nombre primer elemento de la matriz en "collectionitems" a la propiedad Id de el Libro de la clase, el segundo elemento a la NumberOfPages de la propiedad y el elemento final para el Título? No tengo control sobre el JSON de generación en estas circunstancias, y como la solución para trabajar con Silverlight subconjunto de .NET. Sería genial si la solución podría realizar la operación inversa para la serialización así.

19voto

Justin Grant Puntos 25644

Si esto no fuera Silverlight, se podría utilizar IDataContractSurrogate uso de object[] (lo que en realidad está presente en su JSON) en lugar de Book cuando serializar/deserializar. Lamentablemente, IDataContractSurrogate (y las sobrecargas de la DataContractJsonSerializer constructor que lo uso) no están disponibles en Silverlight.

En Silverlight, he aquí un chapucero pero simple solución. Se derivan de la Book clase de un tipo que imlpements ICollection<object>. Dado que el tipo en su serializado JSON es object[], el marco diligentemente serializar en su ICollection<object>, que a su vez se puede envolver con sus propiedades.

La forma más fácil (y hackiest) es sólo para que se derivan de List<object>. Este sencillo hack tiene la desventaja de que los usuarios pueden modificar la lista subyacente de los datos y el desorden de seguridad de sus propiedades. Si eres el único usuario de este código, que puede estar bien. Con un poco más de trabajo, usted puede lanzar su propia aplicación de ICollection y permitir sólo lo suficiente métodos a ejecutar para la serialización de trabajo, y lanzar excepciones para el resto. He incluido ejemplos de código para ambos enfoques a continuación.

Si el de arriba hacks son demasiado feo para usted, estoy seguro de que hay más agraciado maneras de manejar esto. Quizás quieras enfocar su atención en crear una colección personalizada en lugar del tipo de List<Book> de su collectionitems de la propiedad. Este tipo puede contener un campo de tipo List<object[]> (que es el tipo real en su JSON) que usted podría ser capaz de convencer a la serializador para rellenar. Luego de su implementación de IList podría extraer los datos en la Libreta de instancias.

Otra línea de investigación podría tratar de fundición.Por ejemplo, se podría implementar de manera implícita el tipo de conversión entre Book y string[] y sería la serialización de ser lo suficientemente inteligente como para usarlo? Lo dudo, pero puede ser vale la pena intentarlo.

De todos modos, he aquí ejemplos de código para la deriva-de-ICollection hacks se señaló anteriormente. Advertencia: no he verificado estos en Silverlight, pero se debe utilizar sólo en Silverlight accesible tipos así que yo creo (crucemos los dedos!) debería funcionar OK.

Fácil, Hackier De La Muestra

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;

[DataContract]
class BookCollection
{
    [DataMember(Order=1)]
    public string collectionname { get; set; }

    [DataMember(Order = 2)]
    public List<Book> collectionitems { get; set; }
}

[CollectionDataContract]
class Book : List<object>
{
    public string Id { get { return (string)this[0]; } set { this[0] = value; } }
    public int NumberOfPages { get { return (int)this[1]; } set { this[1] = value; } }
    public string Title { get { return (string)this[2]; } set { this[2] = value; } }

}

class Program
{
    static void Main(string[] args)
    {
        DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
        string json = "{"
                    + "\"collectionname\":\"Books\","
                    + "\"collectionitems\": [ "
                            + "[\"12345-67890\",201,\"Book One\"],"
                            + "[\"09876-54321\",45,\"Book Two\"]"
                        + "]"
                    + "}";

        using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
        {
            BookCollection obj = ser.ReadObject(ms) as BookCollection;
            using (MemoryStream ms2 = new MemoryStream())
            {
                ser.WriteObject(ms2, obj);
                string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0,  (int)ms2.Length);
            }
        }
    }
}

Más difícil, un poco menos chapucero de la muestra

Aquí está la segunda muestra, que muestra un manual de aplicación de ICollection, que impide a los usuarios acceder a la colección-- soporta llamando Add() 3 veces (durante la deserialización) pero de lo contrario no se permitirá la modificación a través de la ICollection<T>. El ICollection métodos están expuestos el uso explícito de la interfaz de la aplicación y hay atributos en los métodos para ocultar de intellisense, que debería reducir aún más el hack factor. Pero como usted puede ver que esto es mucho más código.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;

[DataContract]
class BookCollection
{
    [DataMember(Order=1)]
    public string collectionname { get; set; }

    [DataMember(Order = 2)]
    public List<Book> collectionitems { get; set; }
}

[CollectionDataContract]
class Book : ICollection<object>
{
    public string Id { get; set; }
    public int NumberOfPages { get; set; }
    public string Title { get; set; }

    // code below here is only used for serialization/deserialization

    // keeps track of how many properties have been initialized
    [EditorBrowsable(EditorBrowsableState.Never)]
    private int counter = 0;

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void Add(object item)
    {
        switch (++counter)
        {
            case 1:
                Id = (string)item;
                break;
            case 2:
                NumberOfPages = (int)item;
                break;
            case 3:
                Title = (string)item;
                break;
            default:
                throw new NotSupportedException();
        }
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    IEnumerator<object> System.Collections.Generic.IEnumerable<object>.GetEnumerator() 
    {
        return new List<object> { Id, NumberOfPages, Title }.GetEnumerator();
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    {
        return new object[] { Id, NumberOfPages, Title }.GetEnumerator();
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    int System.Collections.Generic.ICollection<object>.Count 
    { 
        get { return 3; } 
    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    bool System.Collections.Generic.ICollection<object>.IsReadOnly 
    { get { throw new NotSupportedException(); } }

    [EditorBrowsable(EditorBrowsableState.Never)]
    void System.Collections.Generic.ICollection<object>.Clear() 
    { throw new NotSupportedException(); }

    [EditorBrowsable(EditorBrowsableState.Never)]
    bool System.Collections.Generic.ICollection<object>.Contains(object item) 
    { throw new NotSupportedException(); }

    [EditorBrowsable(EditorBrowsableState.Never)]
    void System.Collections.Generic.ICollection<object>.CopyTo(object[] array, int arrayIndex) 
    { throw new NotSupportedException(); }

    [EditorBrowsable(EditorBrowsableState.Never)]
    bool System.Collections.Generic.ICollection<object>.Remove(object item) 
    { throw new NotSupportedException(); }
}

class Program
{
    static void Main(string[] args)
    {
        DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
        string json = "{"
                    + "\"collectionname\":\"Books\","
                    + "\"collectionitems\": [ "
                            + "[\"12345-67890\",201,\"Book One\"],"
                            + "[\"09876-54321\",45,\"Book Two\"]"
                        + "]"
                    + "}";

        using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
        {
            BookCollection obj = ser.ReadObject(ms) as BookCollection;
            using (MemoryStream ms2 = new MemoryStream())
            {
                ser.WriteObject(ms2, obj);
                string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0,  (int)ms2.Length);
            }
        }
    }
}

Por CIERTO, la primera vez que leí tu pregunta salté sobre el importante Silverlight requisito. Oops! De todos modos, si no se utiliza Silverlight, aquí está la solución codificado para ese caso, es mucho más fácil y que bien podría guardar aquí para cualquier de Google que viene más adelante.

El (en el regular .NET framework, no Silverlight) la magia que estás buscando es IDataContractSurrogate. Implementar esta interfaz cuando se desea sustituir un tipo de otro tipo cuando serializar/deserializar. En el caso de que se quiera sustituir object[] para Book.

El siguiente código muestra cómo funciona esto:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Collections.ObjectModel;

[DataContract]
class BookCollection
{
    [DataMember(Order=1)]
    public string collectionname { get; set; }

    [DataMember(Order = 2)]
    public List<Book> collectionitems { get; set; }
}

class Book 
{ 
  public string Id { get; set; } 
  public int NumberOfPages { get; set; } 
  public string Title { get; set; } 
} 

// A type surrogate substitutes object[] for Book when serializing/deserializing.
class BookTypeSurrogate : IDataContractSurrogate
{
    public Type GetDataContractType(Type type)
    {
        // "Book" will be serialized as an object array
        // This method is called during serialization, deserialization, and schema export. 
        if (typeof(Book).IsAssignableFrom(type))
        {
            return typeof(object[]);
        }
        return type;
    }
    public object GetObjectToSerialize(object obj, Type targetType)
    {
        // This method is called on serialization.
        if (obj is Book)
        {
            Book book = (Book) obj;
            return new object[] { book.Id, book.NumberOfPages, book.Title };
        }
        return obj;
    }
    public object GetDeserializedObject(object obj, Type targetType)
    {
        // This method is called on deserialization.
        if (obj is object[])
        {
            object[] arr = (object[])obj;
            Book book = new Book { Id = (string)arr[0], NumberOfPages = (int)arr[1], Title = (string)arr[2] };
            return book;
        }
        return obj;
    }
    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null; // not used
    }
    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        return typeDeclaration; // Not used
    }
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null; // not used
    }
    public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
    {
        return null; // not used
    }
    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
        return; // not used
    }
}


class Program
{
    static void Main(string[] args)
    {
        DataContractJsonSerializer ser  =
            new DataContractJsonSerializer(
                typeof(BookCollection), 
                new List<Type>(),        /* knownTypes */
                int.MaxValue,            /* maxItemsInObjectGraph */ 
                false,                   /* ignoreExtensionDataObject */
                new BookTypeSurrogate(),  /* dataContractSurrogate */
                false                    /* alwaysEmitTypeInformation */
                );
        string json = "{"
                    + "\"collectionname\":\"Books\","
                    + "\"collectionitems\": [ "
                            + "[\"12345-67890\",201,\"Book One\"],"
                            + "[\"09876-54321\",45,\"Book Two\"]"
                        + "]"
                    + "}";

        using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
        {
            BookCollection obj = ser.ReadObject(ms) as BookCollection;
            using (MemoryStream ms2 = new MemoryStream())
            {
                ser.WriteObject(ms2, obj);
                string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0,  (int)ms2.Length);
            }
        }
    }
}

1voto

Oleg Puntos 136406

Creo que su pregunta muy interesante. Así que me he gastado mi tiempo en tratar de resolver el problema. Actualmente he recibido un ejemplo que puede serializar y deserealize datos JSON como el siguiente:

{
  "collectionname":"Books",
  "collectionitems":[
    {"book":["12345-67890",201,"Book One"]},
    {"book":["09876-54321",45,"Book Two"]}
  ]
}

el código correspondiente de una pequeña aplicación de consola:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace DataContractJsonSerializer {
    [DataContract]
    class BookCollection {
        [DataMember (Order = 0)]
        public string collectionname { get; set; }
        [DataMember (Order = 1)]
        public List<Book> collectionitems { get; set; }
    }

    [Serializable]
    [KnownType (typeof (object[]))]
    class Book: ISerializable {
        public string Id { get; set; }
        public int NumberOfPages { get; set; }
        public string Title { get; set; }

        public Book () { }

        [SecurityPermissionAttribute (SecurityAction.Demand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        protected Book (SerializationInfo info, StreamingContext context) {
            // called by DataContractJsonSerializer.ReadObject
            Object[] ar = (Object[]) info.GetValue ("book", typeof (object[]));

            this.Id = (string)ar[0];
            this.NumberOfPages = (int)ar[1];
            this.Title = (string)ar[2];
        }

        [SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
        public void GetObjectData (SerializationInfo info, StreamingContext context) {
            // called by DataContractJsonSerializer.WriteObject
            object[] ar = new object[] { (object)this.Id, (object)this.NumberOfPages, (object)this.Title };
            info.AddValue ("book", ar);
        }
    }

    class Program {
        static readonly string testJSONdata = "{\"collectionname\":\"Books\",\"collectionitems\":[{\"book\":[\"12345-67890\",201,\"Book One\"]},{\"book\":[\"09876-54321\",45,\"Book Two\"]}]}";

        static void Main (string[] args) {
            BookCollection test = new BookCollection () {
                collectionname = "Books",
                collectionitems = new List<Book> {
                    new Book() { Id = "12345-67890", NumberOfPages = 201, Title = "Book One"},
                    new Book() { Id = "09876-54321", NumberOfPages = 45, Title = "Book Two"},
                }
            };

            MemoryStream memoryStream = new MemoryStream ();
            System.Runtime.Serialization.Json.DataContractJsonSerializer ser =
                new System.Runtime.Serialization.Json.DataContractJsonSerializer (typeof (BookCollection));
            memoryStream.Position = 0;
            ser.WriteObject (memoryStream, test);

            memoryStream.Flush();
            memoryStream.Position = 0;
            StreamReader sr = new StreamReader(memoryStream);
            string str = sr.ReadToEnd ();
            Console.WriteLine ("The result of custom serialization:");
            Console.WriteLine (str);

            if (String.Compare (testJSONdata, str, StringComparison.Ordinal) != 0) {
                Console.WriteLine ("Error in serialization: unexpected results.");
                    return;
            }

            byte[] jsonDataAsBytes = System.Text.Encoding.GetEncoding ("iso-8859-1").GetBytes (testJSONdata);
            MemoryStream stream = new MemoryStream (jsonDataAsBytes);
            stream.Position = 0;
            BookCollection p2 = (BookCollection)ser.ReadObject (stream);
        }
    }
}

Yo aún no probado este enfoque en virtud de Silverlight 4.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by: