代码之家  ›  专栏  ›  技术社区  ›  user626528

自定义对象序列化与PreserveReferencesHandling

  •  1
  • user626528  · 技术社区  · 6 年前

    有没有标准的方法 "$id" 序列化时当前对象的字段值,并通过其 “id美元” 反序列化时的值,使用自定义 JsonConverter ?

    1 回复  |  直到 6 年前
        1
  •  1
  •   dbc    6 年前

    里面 custom JsonConverter 你可以使用 IReferenceResolver 返回的 JsonSerializer.ReferenceResolver 手动读写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);
    

    笔记:

    样板小提琴 here .