如何使用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
类中的任何额外属性都不考虑在内。
是否有办法将它们包含在序列化中?
问题如下:当一个对象实现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
修饰Count
和IsReadOnly
属性,以防止它们出现在输出中。如果您想将_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"
}
}