Cannot convert Jobject type to a Dictionary<string, EntityProperty>

Viewed 236

How can I convert a JObject to a Dictionary<string, EntityProperty>. I tried this way

data.ToObject<Dictionary<string, EntityProperty>>()

but it gives me error saying ""Error converting value "123" to type 'Microsoft.Azure.Cosmos.Table.EntityProperty'. Path 'Version'."}"

How can I do this? data on debugging gives me this structure:

{
  "Version": "123",
  "Eest": {
    "Bulls": 1848,
    "Message": "Passed (0.325 mA < 2[0] => 'P')",
    "Credit": 0.325,
    "Read": 0.14,
    "SBin": "P"
  },
  "GenericTests": [],
  "HostVersion": "Test",
  "RawSampleIDData": [
    1,
    3,
    2,
    4
  ]
}
1 Answers

You cannot deserialize the JSON object shown in your question to a Dictionary<string, EntityProperty> even with a custom JsonConverter. This is because EntityProperty is a Class for storing information about a single property in an entity in a table. As such, it can capture a variety of .Net primitives including:

  • byte []
  • bool
  • DateTime.
  • double, int and long.
  • string

However, your root JSON object's values included nested arrays ("RawSampleIDData") and objects ("Eest"). As such, it cannot be deserialized to a dictionary of EntityProperty primitives. You will need to adopt a different data model to deserialize that JSON.

Relatedly, see:

If your object values were entirely JSON primitives, you could introduce the following converter:

public class EntityPropertyConverter : JsonConverter<EntityProperty>
{
    public override void WriteJson(JsonWriter writer, EntityProperty value, JsonSerializer serializer) =>
        serializer.Serialize(writer, value.PropertyAsObject);

    public override EntityProperty ReadJson(JsonReader reader, Type objectType, EntityProperty existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        switch (reader.MoveToContentAndAssert().TokenType)
        {
            case JsonToken.Null:
                return null;
            case JsonToken.Integer:
                if (reader.Value is int i)
                    return new EntityProperty(i);
                else if (reader.Value is long l)
                    return new EntityProperty(l);
                // BigInteger not supported.
                break;
            case JsonToken.Float:
                if (reader.Value is double d)
                    return new EntityProperty(d);
                else if (reader.Value is decimal m)
                    return new EntityProperty((double)m);
                break;
            case JsonToken.String:
                return new EntityProperty((string)reader.Value);
            case JsonToken.Boolean:
                return new EntityProperty((bool)reader.Value);
            case JsonToken.Date:
                if (reader.Value is DateTime dt)
                    return new EntityProperty(dt);
                else if (reader.Value is DateTimeOffset dto)
                    return new EntityProperty(dto);
                break;
            case JsonToken.Bytes:
                if (reader.Value is byte [] a)
                    return new EntityProperty(a);
                else if (reader.Value is Guid g)
                    return new EntityProperty(g);
                break;
        }
        throw new JsonSerializationException(string.Format("Cannot convert value {0} to {1}", reader.TokenType, nameof(EntityProperty)));
    }       
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

And then do:

var settings = new JsonSerializerSettings
{
    Converters = { new EntityPropertyConverter() },
};
var dict = data.ToObject<Dictionary<string, EntityProperty>>(JsonSerializer.CreateDefault(settings));

Demo fiddle here.

Update

With the above converter, one option to deserialize an arbitrary JObject to a Dictionary<string, EntityProperty> would be to flatten the object to a dictionary keyed by the path of each primitive values, like so:

var settings = new JsonSerializerSettings
{
    Converters = { new EntityPropertyConverter() },
};
var serializer = JsonSerializer.CreateDefault(settings);
var dict = data.Descendants().OfType<JValue>().ToDictionary(v => v.Path, v => v.ToObject<EntityProperty>(serializer));

For the JSON in the question the result is

{
  "Version": "123",
  "Eest.Bulls": 1848,
  "Eest.Message": "Passed (0.325 mA < 2[0] => 'P')",
  "Eest.Credit": 0.325,
  "Eest.Read": 0.14,
  "Eest.SBin": "P",
  "HostVersion": "Test",
  "RawSampleIDData[0]": 1,
  "RawSampleIDData[1]": 3,
  "RawSampleIDData[2]": 2,
  "RawSampleIDData[3]": 4
}

Demo fiddle #2 here.

Related