Json.NET Deserialization into dynamic object with referencing

前端 未结 2 676
逝去的感伤
逝去的感伤 2020-12-21 01:31

How can I get Json.NET to deserialize into dynamic objects but still do reference resolution?
dynamic d=JsonConvert.DeserializeObject(...)

相关标签:
2条回答
  • 2020-12-21 02:05

    Since Json.NET is open source and its MIT license allows modification, the easiest solution may be to adapt its ExpandoObjectConverter to your needs:

    /// <summary>
    /// Converts an ExpandoObject to and from JSON, handling object references.
    /// </summary>
    public class ObjectReferenceExpandoObjectConverter : JsonConverter
    {
        // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/ExpandoObjectConverter.cs
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // can write is set to false
            throw new NotImplementedException();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return ReadValue(serializer, reader);
        }
    
        private object ReadValue(JsonSerializer serializer, JsonReader reader)
        {
            while (reader.TokenType == JsonToken.Comment)
            {
                if (!reader.Read())
                    throw reader.CreateException("Unexpected end when reading ExpandoObject.");
            }
    
            switch (reader.TokenType)
            {
                case JsonToken.StartObject:
                    return ReadObject(serializer, reader);
                case JsonToken.StartArray:
                    return ReadList(serializer, reader);
                default:
                    if (JsonTokenUtils.IsPrimitiveToken(reader.TokenType))
                        return reader.Value;
                    throw reader.CreateException("Unexpected token when converting ExpandoObject");
            }
        }
    
        private object ReadList(JsonSerializer serializer, JsonReader reader)
        {
            IList<object> list = new List<object>();
    
            while (reader.Read())
            {
                switch (reader.TokenType)
                {
                    case JsonToken.Comment:
                        break;
                    default:
                        object v = ReadValue(serializer, reader);
                        list.Add(v);
                        break;
                    case JsonToken.EndArray:
                        return list;
                }
            }
    
            throw reader.CreateException("Unexpected end when reading ExpandoObject.");
        }
    
        private object ReadObject(JsonSerializer serializer, JsonReader reader)
        {
            IDictionary<string, object> expandoObject = null;
            object referenceObject = null;
    
            while (reader.Read())
            {
                switch (reader.TokenType)
                {
                    case JsonToken.PropertyName:
                        string propertyName = reader.Value.ToString();
                        if (!reader.Read())
                            throw new InvalidOperationException("Unexpected end when reading ExpandoObject.");
                        object v = ReadValue(serializer, reader);
                        if (propertyName == "$ref")
                        {
                            var id = (v == null ? null : Convert.ToString(v, CultureInfo.InvariantCulture));
                            referenceObject = serializer.ReferenceResolver.ResolveReference(serializer, id);
                        }
                        else if (propertyName == "$id")
                        {
                            var id = (v == null ? null : Convert.ToString(v, CultureInfo.InvariantCulture));
                            serializer.ReferenceResolver.AddReference(serializer, id, (expandoObject ?? (expandoObject = new ExpandoObject())));
                        }
                        else
                        {
                            (expandoObject ?? (expandoObject = new ExpandoObject()))[propertyName] = v;
                        }
                        break;
                    case JsonToken.Comment:
                        break;
                    case JsonToken.EndObject:
                        if (referenceObject != null && expandoObject != null)
                            throw reader.CreateException("ExpandoObject contained both $ref and real data");
                        return referenceObject ?? expandoObject;
                }
            }
    
            throw reader.CreateException("Unexpected end when reading ExpandoObject.");
        }
    
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(ExpandoObject));
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    }
    
    public static class JsonTokenUtils
    {
        // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/JsonTokenUtils.cs
        public static bool IsPrimitiveToken(this JsonToken token)
        {
            switch (token)
            {
                case JsonToken.Integer:
                case JsonToken.Float:
                case JsonToken.String:
                case JsonToken.Boolean:
                case JsonToken.Undefined:
                case JsonToken.Null:
                case JsonToken.Date:
                case JsonToken.Bytes:
                    return true;
                default:
                    return false;
            }
        }
    }
    
    public static class JsonReaderExtensions
    {
        public static JsonSerializationException CreateException(this JsonReader reader, string format, params object[] args)
        {
            // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs
    
            var lineInfo = reader as IJsonLineInfo;
            var path = (reader == null ? null : reader.Path);
            var message = string.Format(CultureInfo.InvariantCulture, format, args);
            if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
            {
                message = message.Trim();
                if (!message.EndsWith(".", StringComparison.Ordinal))
                    message += ".";
                message += " ";
            }
            message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
            if (lineInfo != null && lineInfo.HasLineInfo())
                message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
            message += ".";
    
            return new JsonSerializationException(message);
        }
    }
    

    And then use it like:

            var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, ReferenceLoopHandling = ReferenceLoopHandling.Serialize };
            settings.Converters.Add(new ObjectReferenceExpandoObjectConverter());
            dynamic d = JsonConvert.DeserializeObject<ExpandoObject>(json, settings);
    
    0 讨论(0)
  • 2020-12-21 02:13

    The way I did it now is with a postprocessing step and a recursive function that's doing its own reference saving and rewiring:

        private static void Reffing(this IDictionary<string, object> current, Action<object> exchange,IDictionary<string, object> refdic)
        {
            object value;
            if(current.TryGetValue("$ref", out value))
            {
                if(!refdic.TryGetValue((string) value, out value))
                    throw new Exception("ref not found ");
                if (exchange != null)
                    exchange(value);
                return;
            }
            if (current.TryGetValue("$id", out value))
            {
                refdic[(string) value] = current;
            }
            foreach (var kvp in current.ToList())
            {
                if (kvp.Key.StartsWith("$"))
                    continue;
                var expandoObject = kvp.Value as ExpandoObject;
                if(expandoObject != null)
                    Reffing(expandoObject,o => current[kvp.Key]=o,refdic);
                var list = kvp.Value as IList<object>;
                if (list == null) continue;
                for (var i = 0; i < list.Count; i++)
                {
                    var lEO = list[i] as ExpandoObject;
                    if(lEO!=null)
                        Reffing(lEO,o => list[i]=o,refdic);
                }
            }
        }
    

    used as:

            var test = JsonConvert.DeserializeObject<ExpandoObject>(...);
            var dictionary = new Dictionary<string, object>();
            Reffing(test,null,dictionary);
    
    0 讨论(0)
提交回复
热议问题