0

JavaScript/PHP Veteran, .NET Newbie here.

I'm trying to use Json.NET to deserialize JSON data, returned from an API call, into some class instances.

It works well for most API calls when the JSON schema is consistent and matches my classes. However, I have a few JSON objects that return like this:

"searchresult":{ "summary":{
"page":"1 of 451", "relevancy":"100% - 99%", "count":25, "query":"some search query" }, "0":{ "relevance":100, "id":343, }, "1":{ "relevance":99, "id":434, }, "2":{ ... } },

What is the best method for handling an inconsistent schema, like "searchresult"?

I have created a "SearchResult", which has a "Summary" property, however I'm not sure how to map the remaining items.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
Firephp
  • 137
  • 2
  • 8
  • When you say it is inconsistent, do you mean there are a varying number of relevance/Id pairs? Otherwise nothing is inconsistent if we can only see one thing. – Ňɏssa Pøngjǣrdenlarp Dec 09 '17 at 16:54
  • 1
    You can deserialize into a anonymous object. Refer to this answer https://stackoverflow.com/a/33284670/8438534 – Sandip Ghosh Dec 09 '17 at 17:13
  • It think it is inconsistent because you have a varying number of integral properties, and a single named property "summary" inside of the "searchresult" object. I'd imagine it would be better with a named property "summary", and an array of key-pair items. – Firephp Dec 09 '17 at 17:23
  • Thanks Sandip. I'll use the anonymous object. – Firephp Dec 09 '17 at 17:23
  • 1
    Your `"searchresults"` JSON consists of a mixture of fixed and unknown property names. Furthermore, the unknown properties are all typed, i.e. their values have a fixed schema. Given that, you could adopt the approach from [How to deserialize a child object with dynamic (numeric) key names?](https://stackoverflow.com/a/40094403/3744182) and use the `[JsonTypedExtensionData]` implemented in that answer. – dbc Dec 09 '17 at 18:01
  • Ok thank you for the explanation. It makes sense now. Ill check into this. – Firephp Dec 09 '17 at 18:46

1 Answers1

2

In order to give you strong typing, if the schema is always with a:

  • Summary
  • Varying number of "Relevances"

You can a JsonConverter class to deserialize it into a class named Summary, and a List of "Relevances". Like so:

Execution:

string json = @"{
    ""searchresult"":{ 
        ""summary"":{

            ""page"":""1 of 451"", 
            ""relevancy"":""100% - 99%"", 
            ""count"":25, 
            ""query"":""some search query"" 
        }, 
        ""0"":{ 
            ""relevance"":100, 
            ""id"":343, 
        }, 
        ""1"":{ 
            ""relevance"":99, 
            ""id"":434, 
        }
    }
}";

var searchResult = JsonConvert.DeserializeObject<SearchResult>(json);

ViewModels

[JsonConverter(typeof(Converter))]
class SearchResult
{
    public Summary Summary { get; set; }
    public List<RelevanceWrapper> Relevances { get; set; }
}

class Summary
{
    public string Page { get; set; }
    public string Relevancy { get; set; }
    public int Count { get; set; }
    public string Query { get; set; }
}

class RelevanceWrapper
{
    public int Id { get; set; }
    public int Relevance { get; set; }
}

JsonConverter

class Converter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);

        var searchResultToken = jObject.GetValue("searchresult");

        var searchResult = new SearchResult()
        {
            Relevances = new List<RelevanceWrapper>()
        };

        foreach (JProperty prop in searchResultToken)
        {
            if (prop.Name == "summary")
            {
                searchResult.Summary = prop.Value.ToObject<Summary>();
            }
            else
            {
                searchResult.Relevances.Add(prop.Value.ToObject<RelevanceWrapper>());
            }
        }

        return searchResult;
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }
}

There are more ways to do this in ReadJson, as well as other ways of deserializing with Json.Net, but I find this way readable enough to understand what's going on.

tehabstract
  • 529
  • 6
  • 14