当使用自定义JsonConverter
时,是否有任何标准方法可以在序列化时获取当前对象的"$id"
字段值,并在反序列化时通过其"$id"
值获取对象?
在自定义JsonConverter
中,可以使用JsonSerializer.ReferenceResolver
返回的IReferenceResolver
手动读取和写入Json.NET的"$id"
和"$ref"
属性。
以下转换器为此提供了一个模板:
public abstract class ReferenceHandlingCustomCreationConverter<T> : JsonConverter where T : class
{
const string refProperty = "$ref";
const string idProperty = "$id";
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
protected virtual T Create(Type objectType, T existingValue, JsonSerializer serializer, JObject obj)
{
return existingValue ?? (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
}
protected abstract void Populate(JObject obj, T value, JsonSerializer serializer);
protected abstract void WriteProperties(JsonWriter writer, T value, JsonSerializer serializer, JsonObjectContract contract);
public override sealed object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType);
if (!(contract is JsonObjectContract))
{
throw new JsonSerializationException(string.Format("Invalid non-object contract type {0}", contract));
}
if (!(existingValue == null || existingValue is T))
{
throw new JsonSerializationException(string.Format("Converter cannot read JSON with the specified existing value. {0} is required.", typeof(T)));
}
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var refId = (string)obj[refProperty].RemoveFromLowestPossibleParent();
var objId = (string)obj[idProperty].RemoveFromLowestPossibleParent();
if (refId != null)
{
var reference = serializer.ReferenceResolver.ResolveReference(serializer, refId);
if (reference != null)
return reference;
}
var value = Create(objectType, (T)existingValue, serializer, obj);
if (objId != null)
{
// Add the empty array into the reference table BEFORE poppulating it, to handle recursive references.
serializer.ReferenceResolver.AddReference(serializer, objId, value);
}
Populate(obj, value, serializer);
return value;
}
public override sealed void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (!(contract is JsonObjectContract))
{
throw new JsonSerializationException(string.Format("Invalid non-object contract type {0}", contract));
}
if (!(value is T))
{
throw new JsonSerializationException(string.Format("Converter cannot read JSON with the specified existing value. {0} is required.", typeof(T)));
}
writer.WriteStartObject();
if (serializer.ReferenceResolver.IsReferenced(serializer, value))
{
writer.WritePropertyName(refProperty);
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
}
else
{
writer.WritePropertyName(idProperty);
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
WriteProperties(writer, (T)value, serializer, (JsonObjectContract)contract);
}
writer.WriteEndObject();
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContent(this JsonReader reader)
{
if (reader.TokenType == JsonToken.None)
reader.Read();
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}
实现转换器需要实现两种抽象方法:
protected abstract void Populate(JObject obj, T value, JsonSerializer serializer);
protected abstract void WriteProperties(JsonWriter writer, T value, JsonSerializer serializer, JsonObjectContract contract);
一个默认的通用实现可能看起来像:
public class DefaultReferenceHandlingCustomCreationConverter<T> : ReferenceHandlingCustomCreationConverter<T> where T : class
{
protected override void Populate(JObject obj, T value, JsonSerializer serializer)
{
using (var reader = obj.CreateReader())
serializer.Populate(reader, value);
}
protected override void WriteProperties(JsonWriter writer, T value, JsonSerializer serializer, JsonObjectContract contract)
{
foreach (var property in contract.Properties.Where(p => p.Writable && !p.Ignored))
{
// TODO: handle JsonProperty attributes including
// property.Converter, property.IsReference, property.ItemConverter, property.ItemReferenceLoopHandling,
// property.ItemReferenceLoopHandling, property.ObjectCreationHandling, property.ReferenceLoopHandling, property.Required
var itemValue = property.ValueProvider.GetValue(value);
writer.WritePropertyName(property.PropertyName);
serializer.Serialize(writer, itemValue);
}
}
}
使用它,您将串行化如下:
var settings = new JsonSerializerSettings
{
Converters = { new DefaultReferenceHandlingCustomCreationConverter<RootObject>() },
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
};
var json = JsonConvert.SerializeObject(parent, Formatting.Indented, settings);
注:
转换器设计用于处理序列化为JSON对象的c#类。也可以创建一个转换器,手动写入和读取集合的
"$ref"
、"$id"
和"$values"
属性,例如,如的回答所示。无法保留对数组或只读列表的引用,或从非默认构造函数创建的列表。转换器在反序列化期间拆分创建和填充对象的任务,因此将无法处理仅具有参数化构造函数的对象。这是正确解析递归自引用所必需的。
需要使用
ReferenceLoopHandling.Serialize
进行序列化。
此处为小提琴样本。