Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
835 views
in Technique[技术] by (71.8m points)

c# - Json.NET Deserialization into dynamic object with referencing

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)

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.

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

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.

Any help?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...