如何使用Json.Net序列化/反序列化具有其他属性的自定义集合

时间:2013-01-17 16:35:31

标签: c# .net json serialization json.net

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

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类中的任何额外属性都不会被考虑在内。

有没有将它们包含在序列化中?

6 个答案:

答案 0 :(得分:23)

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

 [ {"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());

答案 1 :(得分:8)

就个人而言,我希望尽可能避免编写自定义JsonConverter,而是使用为此目的而设计的各种JSON属性。您可以使用JsonObjectAttribute简单地装饰FooCollection,这会强制序列化为JSON对象而不是数组。您必须使用Count修饰IsReadOnlyJsonIgnore属性,以防止它们出现在输出中。如果您想将_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的定义才能添加这些属性时,这才有效,否则您必须采用自定义转换器的方式。

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

继承自List工作吗?

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

答案 4 :(得分:0)

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

[JsonObject]
public class FooCollection : List<int>
{
    [DataMember]
    public string Bar { get; set; } = "Bar";
    public ICollection<int> Items => new _<int>(this);
}

public class _<T> : ICollection<T>
{
    public _(ICollection<T> collection) => Inner = collection;    
    public ICollection<T> 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<T> 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 } =&gt;

{
  "Bar": "Bar",
  "Items": [
    1,
    2,
    3
  ],
  "Capacity": 4,
  "Count": 3
}

new FooCollection { 1, 2, 3 }.ToArray() =&gt; new []{1, 2, 3}.ToArray()

答案 5 :(得分:0)

我最近正在测试类似的东西,并提出了以下不需要自定义序列化程序的示例(与 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"
  }
}