如何使用 Json.Net 序列化/反序列化具有附加属性的自定义集合
Posted
技术标签:
【中文标题】如何使用 Json.Net 序列化/反序列化具有附加属性的自定义集合【英文标题】:How to serialize/deserialize a custom collection with additional properties using Json.Net 【发布时间】:2013-01-01 06:24:21 【问题描述】:我有一个自定义集合(实现 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
类中的任何额外属性都不会被考虑在内。
到底有没有将它们包含在序列化中?
【问题讨论】:
【参考方案1】:问题如下:当一个对象实现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());
【讨论】:
这对我有帮助,但是当我使用Foo[]
作为代理中的类型时,JSON.NET 不喜欢它 - 我不得不使用 IEnumerable<Foo>
嗯...奇怪,你用的是什么版本的 json.net?
不确定,目前无法检查,但来自针对 .NET 4.0 的掘金
我刚刚检查过,它适用于 4.5.11.15520 和 5.0.8.16617 版本......我怀疑你做的事情与上面的代码略有不同......
@Jeff:我已经用列表替换了数组,所以在 preserve-reference = true 的情况下人们不会崩溃【参考方案2】:
我个人喜欢尽可能避免编写自定义JsonConverter
s,而是使用为此目的设计的各种 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
的定义以添加这些属性时才有效,否则您必须采用自定义转换器。
【讨论】:
因为我使用的是持久化 json 文档的 c# 模型,所以这对我来说是最好的选择。我完全可以用反映其物理持久性的信息来装饰我的模型。在这种情况下,文档是 100% 耦合到 json 的。 我认为你可以通过显式实现接口来避免JsonIgnore
。
今天我了解到,使用显式接口不会序列化这些成员。问题是我不知道为什么?这个 Little Endian 背后的故事是什么
非常感谢【参考方案3】:
如果您不想编写自定义 JsonConverter
或使用 JSON 属性 (JsonObjectAttribute),您可以使用以下扩展方法:
public static string ToFooJson<T>(this FooCollection fooCollection)
return JsonConvert.SerializeObject(new
Bar = fooCollection.Bar,
Collection = fooCollection
);
【讨论】:
【参考方案4】:如果您还想保留 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()
【讨论】:
【参考方案5】:从 List 继承有效吗?
class FooCollection : List<Foo>, IList<Foo>
public string Bar get; set;
//Implement IList, ICollection and IEnumerable members...
【讨论】:
令人惊讶的是,除非绝对必要,否则我会走那条路,理想情况下我想保持继承不变。 目前你正在尝试序列化一个私有属性——通过继承你成为你要求序列化的对象(这显然不是唯一的方法,但很可能是最简单的)跨度> 正如@digEmAll 指出的那样,这不起作用,因为 JSON.net 使用集合序列化程序,它不检查其他属性。但是,我必须同意 qujck 的观点,即如果您的类是 List,那么最好直接从 List 继承,而不必实现整个 IList 接口。唯一的例外是它已经必须实现一些其他的胖基类。【参考方案6】:我最近在测试类似的东西,并提出了以下不需要自定义序列化程序的示例(与 @Ahmed Alejo's answer 非常相似,但不需要公共属性):
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"
【讨论】:
以上是关于如何使用 Json.Net 序列化/反序列化具有附加属性的自定义集合的主要内容,如果未能解决你的问题,请参考以下文章
使用 JSON.NET 反序列化 DateTime 时如何保留时区? [复制]
使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON.net 的json的序列化与反序列化