Json.NET通过引用反序列化为动态对象

时间:2022-08-23 07:57:32

How can I get Json.NET to deserialize into dynamic objects but still do reference resolution?
dynamic d=JsonConvert.DeserializeObject<ExpandoObject>(...) just as
dynamic d=JsonConvert.DeserializeObject(...) returns a dynamic object but they don't resolve the $ref and $id parts. (An ExpandoObject eo for example will only have eo["$ref"]="..." and doesn't have the properties it should have because it's not the same as the $id-Object)

如何让Json.NET反序列化为动态对象,但仍然可以进行参考分辨率?动态d = JsonConvert.DeserializeObject (...)就像动态d = JsonConvert.DeserializeObject(...)返回动态对象但它们不解析$ ref和$ id部分。 (例如,ExpandoObject eo只有eo [“$ ref”] =“...”并且没有它应该具有的属性,因为它与$ id-Object不同

What I've found out is that I need the contract resolver resolve to a dynamic contract - which ExpandoObject only does if I explicitly tell Json.NET with a custom ContractResolver.

我发现的是我需要将契约解析器解析为动态合约 - 只有在我明确告诉Json.NET使用自定义ContractResolver时,ExpandoObject才会这样做。

Still It seems the ExpandoObject is parsed with it's own Converter and it fails again.

仍然看起来ExpandoObject是用它自己的Converter解析的,它再次失败。

I've tried a custom class inheriting from IDynamicMetaObjectProvider which resulted in an infinite loop and didn't seem like the right thing. I would actually expect some easy solution to get ExpandoObject to have reference resolution.

我尝试过继承自IDynamicMetaObjectProvider的自定义类,导致无限循环,看起来不是正确的事情。我实际上期望一些简单的解决方案让ExpandoObject具有参考分辨率。

Any help?

2 个解决方案

#1


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

由于Json.NET是开源的并且其MIT许可允许修改,因此最简单的解决方案可能是使其ExpandoObjectConverter适应您的需求:

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

#2


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

#1


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

由于Json.NET是开源的并且其MIT许可允许修改,因此最简单的解决方案可能是使其ExpandoObjectConverter适应您的需求:

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

#2


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