2

I want to deserialize JSON to list of object, The JSON has structure like this.

{
    "metadata":{ I don't care about metadata },
    "results": [
        { object that I really want },
        { object that I really want },
        { object that I really want }
            ...
    ]
}

I want to get only list of object inside results node and because there are some properties that I want to deserialze it myself, so I implement JsonConverter using implementation from Alain's answer in "How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?", he created JsonConverter derived generic class called JsonCreationConverter<T> that have protect abstract Create method, which actually deserialize JSON, which in turn get called by JsonConverter's ReadJson.

My derived class's signature and its Create signature is like

public class BoringTypeConverter: JsonCreationConverter<List<BoringType>>
{
    protected override List<BoringType> Create(Type objectType, JObject jObject)
    {
        List<BoringType> boringTypes = new List<BoringType>();
        JArray results = (JArray)jObject["results"];

        // deserialize logic
        ...

        return boringTypes;
    }
}

And I used it like this

JsonConvert.DeserializeObject<List<BoringType>>(jsonString, new BoringTypeConverter());

While I was debuging test, I found that Create method successfully deserialize JSON to List<BoringType> but as soon as I hit serializer.Populate(jObjectReader, target) I got error Cannot populate JSON object onto type 'System.Collections.Generic.List1[BoringType]'. Path 'metadata', line 2, position 15.

So I want to know that is the problem here?
Create method didn't do anything about metadata field then why it complain about metadata?

witoong623
  • 1,179
  • 1
  • 15
  • 32
  • 1
    Is your data model actually polymorphic? [How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?](https://stackoverflow.com/q/8030538/3744182) describes how to deserialize items in a *list of polymorphic objects*. Does that apply here? Can you provide a [mcve]? – dbc Aug 02 '18 at 20:37
  • @dbc No, it isn't. Thank you for pointing me out, I didn't read the question carefully, so I though that answer is about how to use JsonConverter now I understand what I was actually doing. I will try to improve example in question next time. – witoong623 Aug 03 '18 at 19:57

2 Answers2

3

As @dbc pointing me out, that question How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? was actually about how to tell JsonConvert.DeserializeObject which type of object should it deserialize to.

So, what I was actually doing was that I deserialized object and then I told JsonConvert to put JSON into my BoringType which doesn't contain metadata field. So that why I got the error literally saying it couldn't put JSON into class that doesn't have corresponding field.

It turn out that writing custom JsonConverter is pretty simple (once you know JSON to Linq).
My converter look like this.

public class BoringTypeConverter : JsonConverter<List<BoringType>>
{
    public override bool CanRead => true;

    public override List<BoringType> ReadJson(JsonReader reader, Type objectType, List<BoringType> existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        List<BoringType> boringTypes = new List<BoringType>();

        var jObject = JObject.Load(reader);
        JArray results = (JArray)jObject["results"];

        foreach (var bor in results)
        {
            var boring = new BoringType();

            var region = (string)bor["region"];
            var source = (string)bor["source"];
            JToken source = (string)bor["source"];
            JToken target = (string)bor["target"];

            boring.Region = region;
            boring.Source = source;
            boring.Source = (string)source["id"];
            boring.Target = (string)target["id"];

            boringTypes.Add(boring);
        }

        return boringTypes;
    }

    public override void WriteJson(JsonWriter writer, List<BoringType> value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
witoong623
  • 1,179
  • 1
  • 15
  • 32
2

I create an simple example how you may do that:

  1. JsonBase is a class that is responsible for data deserialization

    public static class JsonBase<T> where T: BaseData<T> 
    {
        public static List<T> ReturnResultsList(string json)
        {
            var returnData = JsonConvert.DeserializeObject<BaseData<T>>(json);
            return returnData.results;
        }
        public static  string ReturnMetaData(string json)
        {
            var returnData = JsonConvert.DeserializeObject<BaseData<T>>(json);
            return returnData.metadata;
        }
    }
    
  2. BaseData is the class that may contain different types of data since it is generic, and it contains the same properties as your JSON

    public class BaseData<T> where T: class 
    {
        public string metadata { get; set; }
        public List<T> results { get; set; }
    }
    
  3. SomeObjectTHatUwant is object that you really want

    public class SomeObjectTHatUwant:BaseData<SomeObjectTHatUwant>
    {
        public string category { get; set; }/// property for my case
        public string quantity { get; set; }
    
    }
    
  4. In your repository, or some class put this method and fields:

    string url = "http://readabook.16mb.com/api/allcategory";///this link return an json
    
    List<SomeObjectTHatUwant> Response = new List<SomeObjectTHatUwant>();///the data you want
    
    private async Task LoadDataAsync(string uri)
    {
        string responseJsonString = null;
        using (var httpClient = new WebClient())
        {
            try
            {
                responseJsonString = httpClient.DownloadString(uri);
                Response = JsonBase<SomeObjectTHatUwant>.ReturnResultsList(responseJsonString);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
    
  5. Call LoadDataAsync(url); this method initialize in our case Respone field, which is a list of SomeObjectTHatUwant.

(PS. For method LoadedDataAsync I use library System.Net.Http and System.Threading.Tasks) Code example: https://github.com/IonCojucovschi/JsonDeserializeGenericForm

Hope this help.

Ion Cojucovschi
  • 214
  • 2
  • 9