如何使用Json.Net序列化/反序列化带有附加属性的自定义集合

本文关键字:属性 集合 自定义 Json 何使用 Net 序列化 反序列化 | 更新日期: 2023-09-27 17:53:53

我有一个自定义集合(实现illist),它有一些自定义属性,如下所示:

class FooCollection : IList<Foo> {
    private List<Foo> _foos = new List<Foo>();
    public string Bar { get; set; }        
    //Implement IList, ICollection and IEnumerable members...
}

序列化时,使用以下代码:

JsonSerializerSettings jss = new JsonSerializerSettings() {
    TypeNameHandling = TypeNameHandling.Auto
};
string serializedCollection = JsonConvert.SerializeObject( value , jss );

正确地序列化和反序列化所有集合项;但是,FooCollection类中的任何额外属性都不考虑在内。

是否有办法将它们包含在序列化中?

如何使用Json.Net序列化/反序列化带有附加属性的自定义集合

问题如下:当一个对象实现IEnumerable时,JSON.net将其识别为值数组并按照数组Json语法(不包括属性)序列化它,例如:

 [ {"FooProperty" : 123}, {"FooProperty" : 456}, {"FooProperty" : 789}]

如果你想序列化它保持属性,你需要通过定义一个自定义的JsonConverter来手动处理该对象的序列化:

// 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<Foo> 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)
    {
        // N.B. null handling is missing
        var surrogate = serializer.Deserialize<FooCollectionSurrogate>(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)
    {
        // N.B. 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);
    }
}

然后按如下方式使用:

var ss = JsonConvert.SerializeObject(collection, new FooCollectionConverter());
var obj = JsonConvert.DeserializeObject<FooCollection>(ss, new FooCollectionConverter());

我个人喜欢尽可能避免编写自定义的JsonConverter,而是使用为此目的而设计的各种JSON属性。您可以简单地用JsonObjectAttribute修饰FooCollection,这将强制序列化为JSON对象而不是数组。您必须用JsonIgnore修饰CountIsReadOnly属性,以防止它们出现在输出中。如果您想将_foos保留为私有字段,则还必须使用JsonProperty来修饰它。

[JsonObject]
class FooCollection : IList<Foo> {
    [JsonProperty]
    private List<Foo> _foos = new List<Foo>();
    public string Bar { get; set; }  
    // IList implementation
    [JsonIgnore]
    public int Count { ... }
    [JsonIgnore]
    public bool IsReadOnly { ... }
}

序列化产生如下内容:

{
  "_foos": [
    "foo1",
    "foo2"
  ],
  "Bar": "bar"
}

显然,这只适用于如果你能够改变FooCollection的定义,以增加这些属性,否则你必须走自定义转换器的方式。

如果您不想编写自定义JsonConverter或使用JSON属性(JsonObjectAttribute),您可以使用以下扩展方法:

public static string ToFooJson<T>(this FooCollection fooCollection)
{
     return JsonConvert.SerializeObject(new
     {
         Bar = fooCollection.Bar,
         Collection = fooCollection
     });
}

如果您还想保留List或集合本身的内容,您应该考虑公开一个属性来返回列表。必须对其进行包装,以防止序列化时出现循环问题:

注:此解决方案同时支持序列化/反序列化

[JsonObject]
public class FooCollection : List<int>
{
    public string Bar { get; set; } = "Bar";
    [JsonProperty]
    ICollection<int> Items => new _<int>(this);
}
internal class _<T> : ICollection<T>
{
    public _(ICollection<T> collection) => inner = collection;
    private ICollection<T> inner;
    int ICollection<T>.Count => inner.Count;
    bool ICollection<T>.IsReadOnly => inner.IsReadOnly;
    void ICollection<T>.Add(T item) => inner.Add(item);
    void ICollection<T>.Clear() => inner.Clear();
    bool ICollection<T>.Contains(T item) => inner.Contains(item);
    void ICollection<T>.CopyTo(T[] array, int arrayIndex) => inner.CopyTo(array, arrayIndex);
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => inner.GetEnumerator();
    bool ICollection<T>.Remove(T item) => inner.Remove(item);
    IEnumerator IEnumerable.GetEnumerator() => 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()

从List继承工作吗?

class FooCollection : List<Foo>, IList<Foo>
{
    public string Bar { get; set; }        
    //Implement IList, ICollection and IEnumerable members...
}

我最近正在测试类似的东西,并提出了以下示例(非常类似于@Ahmed Alejo的答案,但不需要公共属性),它不需要自定义序列化器:

void Main()
{
    var test = new Container();
    test.Children.Add("Item 1");
    test.Children.Add("Item 2");
    test.Children.Add("Item 3");
    
    Console.WriteLine(JsonConvert.SerializeObject(test));
}
class Container {
    public CustomList Children { get; set; } = new CustomList() { Name = Guid.NewGuid().ToString() };
}
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
class CustomList : IList<string>
{
    private List<string> items = new List<string>();
    private string name;
    public string this[int index] { get => items[index]; set => items[index] = value; }
    public string Name { get => name; set { name = value; } }
    public int Count => items.Count;
    public bool IsReadOnly => false;
    public void Add(string item)
    {
        items.Add(item);
    }
    public void Clear()
    {
        items.Clear();
    }
    public bool Contains(string item) => items.Contains(item);
    public void CopyTo(string[] array, int arrayIndex)
    {
        items.CopyTo(array, arrayIndex);
    }
    public IEnumerator<string> GetEnumerator() => items.GetEnumerator();
    public int IndexOf(string item) => items.IndexOf(item);
    public void Insert(int index, string item)
    {
        items.Insert(index, item);
    }
    public bool Remove(string item) => items.Remove(item);
    public void RemoveAt(int index)
    {
        items.RemoveAt(index);
    }
    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)items).GetEnumerator();
}

关键在于将显式的JsonObjectAttribute添加到类中,这将把它视为对象而不是数组(也可以使用JsonArrayAttribute显式表示)。上述测试的(美化)输出如下所示:

{
  "Children": {
    "items": [
      "Item 1",
      "Item 2",
      "Item 3"
    ],
    "name": "ff8768f7-5efb-4622-b7e2-f1472e80991c"
  }
}