什么问题?
NewtonSoft.Json
是我们最常用的Json组件库之一了。这里来讨论下使用NewtonSoft.Json
序列化List<T>
子类的情景。序列化使用了类JsonSerializer
。
情景重现
如果我们有一个Field
实体类。另有一个FieldGroup
类表示Field
的分组,并携带组属性GroupFormat
。我们需要序列化这个FieldGroup
,该如何实现呢?
机智如我,这么写了:
// FieldGroup 实现类
public class FieldGroup : List<Field>
{
public Format GroupFormat{ get;set; }
}
// 序列化过程
public void main()
{
var group = new FieldGroup()
{
GroupFormat = "Format 1"
};
group.Add(new Field()
{
Name = "Field 1"
});
Console.WriteLine(JsonUtil.SerializeByNsj(group));
}
结果我很纳闷儿,GroupFormat属性被JsonSerializer
吃了吗?
[
{
"Name": "Field 1"
}
]
咋解决呢?
既然JsonSerializer
不会自己处理这个GroupFormat
属性,那我来告诉你它是必须要序列化的!我们使用JsonObject(MemberSerialization.OptOut)
来标记这个类除了显示地标记了JsonIgnore
特性的公有属性都需要被序列化。
[JsonObject(MemberSerialization.OptOut)]
public class FieldGroup : List<Field>
{
public string Format { get; set; }
}
这下好了吧?emmmmmmm......
{
"Format": "Format 1",
"Capacity": 4,
"Count": 1
}
!!!又把List
吃了吗?
原因分析
这点也是在*上看到的。Json数组本身就是纯数组,并不能在数组上应用一个属性。
我的解决方法
既然Json不能支持带属性的数组,那只能在代码里面通过索引模拟一个类似于集合的类了。
[JsonObject(MemberSerialization.OptOut)]
public class FieldGroup : IEnumerable<Field>
{
public PrintFormat GroupFormat { get; set; } = new PrintFormat();
// 使用内部的 List<Field> 代替继承,可直接被序列化和反序列化
public List<Field> Fields { get; set; } = new List<Field>();
// 使用索引对外提供类似于List<T>的访问方式;
public Field this[int index]
{
get => Fields[index];
set => Fields[index] = value;
}
// 提供List<T>一致的Add方法,有需要可以提供其他方法
public void Add(Field field)
{
Fields.Add(field);
}
// 提供类似于List<T>的IEnumerable功能
public IEnumerator<Field> GetEnumerator()
{
return Fields.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
这次的结果是?
{
"Fields": [
{
"Name": "Field 1"
}
],
"Format": "Format 1"
}
老外有话说
Is there any way to JSON.NET-serialize a subclass of List that also has extra properties?
public class LocationListJsonConverter : JsonConverter
{
public override bool CanConvert(System.Type objectType)
{
return objectType == typeof(LocationList);
}
public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer)
{
var locationList = (existingValue as LocationList) ?? new LocationList();
var jLocationList = JObject.ReadFrom(reader);
locationList.IsExpanded = (bool)(jLocationList["IsExpanded"] ?? false);
var jLocations = jLocationList["_Items"];
if(jLocations != null)
{
foreach(var jLocation in jLocations)
{
var location = serializer.Deserialize<Location>(new JTokenReader(jLocation));
locationList.Add(location);
}
}
return locationList;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var locationList = value as LocationList;
JObject jLocationList = new JObject();
if(locationList.IsExpanded)
jLocationList.Add("IsExpanded", true);
if(locationList.Count > 0)
{
var jLocations = new JArray();
foreach(var location in locationList)
{
jLocations.Add(JObject.FromObject(location, serializer));
}
jLocationList.Add("_Items", jLocations);
}
jLocationList.WriteTo(writer);
}
}