4

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?

2 Answers 2

2

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);
Sign up to request clarification or add additional context in comments.

1 Comment

oh, I missed the fact, that the Converter actually gets the Serializer as a parameter for using the ReferenceResolver. I was hesitant of a similar solution as I thought this would be so much copy pasting...
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.