自定义对象序列化与PreserveReferencesHandling [英] Custom object serialization vs PreserveReferencesHandling

查看:82
本文介绍了自定义对象序列化与PreserveReferencesHandling的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有任何标准方法可以在序列化时获取当前对象的"$id"字段值,并在使用自定义JsonConverter进行反序列化时通过其"$id"值获取对象?

Is there any standard way to get "$id" field's value for the current object when serializing, and get the object by its "$id" value when deserializing, when using a custom JsonConverter?

推荐答案

自定义JsonConverter ,您可以使用 IReferenceResolver JsonSerializer.ReferenceResolver 返回以手动读取和写入Json. NET的"$id""$ref"属性.

以下转换器为此提供了一个模板:

The following converter provides a template for this:

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;
    }
}

实现转换器需要实现两种抽象方法:

Implementing the converter requires implementing the two abstract methods:

protected abstract void Populate(JObject obj, T value, JsonSerializer serializer);

protected abstract void WriteProperties(JsonWriter writer, T value, JsonSerializer serializer, JsonObjectContract contract);

一个默认的通用实现可能如下所示:

One default generic implementation might look like:

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);
        }
    }
}

使用它,然后您将进行如下序列化:

Using it, then you would serialize as follows:

var settings = new JsonSerializerSettings
{
    Converters = { new DefaultReferenceHandlingCustomCreationConverter<RootObject>() },
    ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
};
var json = JsonConvert.SerializeObject(parent, Formatting.Indented, settings);

注意:

  • 该转换器设计为与序列化为JSON对象的c#类一起使用.还可以创建一个转换器来手动写入和读取集合的"$ref""$id""$values"属性,例如如此答案所示,

  • The converter is designed to work with c# classes that are serialized as JSON objects. It would also be possible to create a converter that manually writes and reads "$ref", "$id" and "$values" properties for collections, e.g. as shown in this answer to Cannot preserve reference to array or readonly list, or list created from a non-default constructor.

转换器将反序列化过程中创建和填充对象的任务分解开来,因此,仅使用参数化构造函数的对象将无法使用该转换器.这是使递归自引用正确解析所必需的.

The converter splits up the tasks of creating and populating the object during deserialization, and thus will not work with objects with only parameterized constructors. This is required to make recursive self-references resolve correctly.

需要使用ReferenceLoopHandling.Serialize序列化.

样本小提琴此处.

这篇关于自定义对象序列化与PreserveReferencesHandling的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆