Tengo una colección personalizada (implementa IList) que tiene algunas propiedades personalizadas como se muestra a continuación:
class FooCollection : IList { private List _foos = new List(); public string Bar { get; set; } //Implement IList, ICollection and IEnumerable members... }
Cuando serializo, uso el siguiente código:
JsonSerializerSettings jss = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto }; string serializedCollection = JsonConvert.SerializeObject( value , jss );
Serializa y deserializa todos los elementos de la colección correctamente; sin embargo, no se tienen en cuenta las propiedades adicionales en la clase FooCollection
.
¿Hay alguna forma de incluirlos en la serialización?
El problema es el siguiente: cuando un objeto implementa IEnumerable
, JSON.net lo identifica como una matriz de valores y lo serializa siguiendo la syntax Json de la matriz (que no incluye propiedades), por ejemplo:
[ {"FooProperty" : 123}, {"FooProperty" : 456}, {"FooProperty" : 789}]
Si desea serializarlo manteniendo las propiedades, debe manejar la serialización de ese objeto manualmente definiendo un JsonConverter
personalizado:
// intermediate class that can be serialized by JSON.net // and contains the same data as FooCollection class FooCollectionSurrogate { // the collection of foo elements public List Collection { get; set; } // the properties of FooCollection to serialize public string Bar { get; set; } } public class FooCollectionConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(FooCollection); } public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // NB null handling is missing var surrogate = serializer.Deserialize(reader); var fooElements = surrogate.Collection; var fooColl = new FooCollection { Bar = surrogate.Bar }; foreach (var el in fooElements) fooColl.Add(el); return fooColl; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // NB null handling is missing var fooColl = (FooCollection)value; // create the surrogate and serialize it instead // of the collection itself var surrogate = new FooCollectionSurrogate() { Collection = fooColl.ToList(), Bar = fooColl.Bar }; serializer.Serialize(writer, surrogate); } }
Entonces úsalo de la siguiente manera:
var ss = JsonConvert.SerializeObject(collection, new FooCollectionConverter()); var obj = JsonConvert.DeserializeObject(ss, new FooCollectionConverter());
Personalmente, me gusta evitar escribir JsonConverter
personalizado JsonConverter
que sea posible, y en su lugar utilizar los diversos atributos JSON que fueron diseñados para este propósito. Simplemente puede decorar FooCollection
con JsonObjectAttribute
, que fuerza la serialización como un objeto JSON en lugar de una matriz. Tendría que decorar las propiedades Count
e IsReadOnly
con JsonIgnore
para evitar que aparezcan en la salida. Si desea mantener _foos
un campo privado, también debería decorarlo con JsonProperty
.
[JsonObject] class FooCollection : IList { [JsonProperty] private List _foos = new List (); public string Bar { get; set; } // IList implementation [JsonIgnore] public int Count { ... } [JsonIgnore] public bool IsReadOnly { ... } }
La serialización produce algo como lo siguiente:
{ "_foos": [ "foo1", "foo2" ], "Bar": "bar" }
Obviamente, esto solo funciona si puede cambiar la definición de FooCollection
para agregar esos atributos; de lo contrario, debe seguir el camino de los convertidores personalizados.
¿Funciona heredar de la lista?
class FooCollection : List, IList { public string Bar { get; set; } //Implement IList, ICollection and IEnumerable members... }
Si también desea conservar los contenidos de la Lista o colección propiamente dicha, podría considerar la posibilidad de exponer la propiedad a la lista. Tiene que ser envolvente para evitar problemas cíclicos durante la serialización:
[JsonObject] public class FooCollection : List { [DataMember] public string Bar { get; set; } = "Bar"; public ICollection Items => new _ (this); } public class _ : ICollection { public _(ICollection collection) => Inner = collection; public ICollection Inner { get; } public int Count => this.Inner.Count; public bool IsReadOnly => this.Inner.IsReadOnly; public void Add(T item) => this.Inner.Add(item); public void Clear() => this.Inner.Clear(); public bool Contains(T item) => this.Inner.Contains(item); public void CopyTo(T[] array, int arrayIndex) => this.Inner.CopyTo(array, arrayIndex); public IEnumerator GetEnumerator()=> this.Inner.GetEnumerator(); public bool Remove(T item) => this.Inner.Remove(item); IEnumerator IEnumerable.GetEnumerator() => this.Inner.GetEnumerator(); }
new FooCollection { 1, 2, 3, 4, 4 }
=>
{ "Bar": "Bar", "Items": [ 1, 2, 3 ], "Capacity": 4, "Count": 3 }
new FooCollection { 1, 2, 3 }.ToArray()
=> new []{1, 2, 3}.ToArray()