c# - supera - maxjsondeserializermembers maximum value
Deserialice los valores de matriz a las propiedades.NET utilizando DataContractJsonSerializer (2)
Estoy trabajando con el DataContractJsonSerializer en Silverlight 4 y me gustaría deserializar el siguiente JSON:
{
"collectionname":"Books",
"collectionitems": [
["12345-67890",201,
"Book One"],
["09876-54321",45,
"Book Two"]
]
}
En 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 asignar el primer elemento de matriz sin nombre en "collectionitems" a la propiedad Id de la clase Book, el segundo elemento a la propiedad NumberOfPages y el elemento final al título? No tengo control sobre la generación de JSON en este caso y me gustaría que la solución funcione con el subconjunto de Silverlight de .NET. Sería genial si la solución pudiera realizar lo contrario para la serialización también.
Encuentro tu pregunta muy interesante. Así que tengo que pasar mi tiempo tratando de resolver el problema. Actualmente recibí un ejemplo que puede serializar y deserealizar datos JSON como los siguientes:
{
"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);
}
}
}
Todavía no he probado este enfoque bajo Silverlight 4.
Si esto no fuera Silverlight, podría usar IDataContractSurrogate
para usar el object[]
(lo que realmente está presente en su JSON) en lugar de Book
al serializar / deserializar. Lamentablemente, IDataContractSurrogate
(y las sobrecargas del constructor DataContractJsonSerializer
que lo utilizan) no están disponibles en Silverlight.
En Silverlight, aquí hay una solución hacky pero simple. Derive la clase Book
de un tipo que imlpements ICollection<object>
. Dado que el tipo en su JSON serializado es el object[]
, el marco lo serializará debidamente en su ICollection<object>
, que a su vez puede ajustar con sus propiedades.
Lo más fácil (y lo más difícil) es derivar de la List<object>
. Este sencillo truco tiene la desventaja de que los usuarios pueden modificar los datos de la lista subyacente y desordenar sus propiedades. Si eres el único usuario de este código, eso podría estar bien. Con un poco más de trabajo, puede implementar su propia implementación de ICollection
y permitir solo los métodos suficientes para ejecutar la serialización para que funcione, y lanzar excepciones para el resto. Incluí ejemplos de código para ambos enfoques a continuación.
Si los trucos anteriores son demasiado feos para ti, estoy seguro de que hay maneras más elegantes de manejar esto. Probablemente querrá centrar su atención en la creación de un tipo de colección personalizado en lugar de List<Book>
para su propiedad collectionitems
. Este tipo podría contener un campo de tipo List<object[]>
(que es el tipo real en su JSON) que podría convencer al serializador para que rellene. Entonces su implementación de IList podría extraer esos datos en instancias reales de Book.
Otra línea de investigación podría intentar el lanzamiento. Por ejemplo, ¿podría implementar una conversión de tipo implícita entre Book
y string[]
y la serialización sería lo suficientemente inteligente como para usarla? Lo dudo, pero puede valer la pena intentarlo.
De todos modos, aquí hay ejemplos de código para los hacks derivados de ICollection mencionados anteriormente. Advertencia: no he verificado esto en Silverlight, pero deberían estar usando solo tipos accesibles a Silverlight, así que creo que (con los dedos cruzados) debería funcionar bien.
Fácil, Hackier 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);
}
}
}
}
Muestra más dura, ligeramente menos hacky.
Esta es la segunda muestra, que muestra una implementación manual de ICollection, que evita que los usuarios accedan a la colección. Admite llamar a Add()
3 veces (durante la deserialización), pero no permite la modificación a través de ICollection<T>
. Los métodos de ICollection se exponen mediante la implementación de la interfaz explícita y existen atributos en esos métodos para ocultarlos de la inteligencia, lo que debería reducir aún más el factor de pirateo. Pero como puedes ver este es un código mucho más.
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í su pregunta me salté el importante requisito de Silverlight. Ups! De todos modos, si no utilizo Silverlight, aquí está la solución que codifiqué para ese caso: es mucho más fácil y es mejor que la guarde aquí para cualquier Googlers que llegue más tarde.
La magia (en el marco .NET normal, no Silverlight) que está buscando es IDataContractSurrogate . Implemente esta interfaz cuando desee sustituir un tipo por otro al serializar / deserializar. En su caso, debe sustituir el object[]
por Book
.
Aquí hay un código que 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);
}
}
}
}