1

I am trying to deserialize a Json response from an API.

The data looks like this

{
"response": {
    "6112": {
        "ID": 6112,
        "Title": "AdditionalPhotos"
    },
    "5982": {
        "ID": 5982,
        "Title": "BikeRide"
    },
    "total_records": "20",
    "returned_count": 10,
    "returned_records": "1-10"

}
}

C# class:

public class Products
{
    public class Product
    {
        public string Id { get; set; }
        public string Title { get; set; }
    }
    public Product product { get; set; }
}

public class ss
{
    public Dictionary<string, Products.Product> Response { get; set; }
    public string total_records { get; set; }

}

Serialization code

 ss res = Newtonsoft.Json.JsonConvert.DeserializeObject<ss>(jsonData());

I can get it to work without the total_records entry and below by deserializng to a Dictionary <string , Product>. But I cannot figure out how to get it to work. This is the error I get

Error converting value "20" to type 'Products+Product'. Path 'response.total_records'

I know why I get the error, but I'm unsure how I can proceed without going in and substringing from total_records down. I have no control over the API data.

Edit: you guys are fast, I was still getting to putting the classes up

Andrew MacNaughton
  • 783
  • 2
  • 6
  • 21
  • 1
    Is this JSON objcet valid? Did you try http://json2csharp.com/? – h__ Oct 19 '14 at 19:11
  • Thanks Krzysztof, I hadn't tried that but i get an individual class for every entry. @StephenKennedy. I know the error is being caused because the deserializer is trying to convert "20" to a product object which it isnt. I just can't figure out how i can deserialize the Json into a logical list of objects. – Andrew MacNaughton Oct 19 '14 at 19:23
  • That's the problem alright. The JSON isn't terribly well formed as you'd ideally want a Products property containing what is effectively an array, alongside the other properties such as total_records which is a numeric string and returned_count which is a number. Also there appears to be a curly bracket missing in the data... You say you have no control over the API, does the publisher not provide an SDK? – Stephen Kennedy Oct 19 '14 at 19:32
  • 1
    @StephenKennedy thanks for the reply. I am in agreeance with you. I would wrap the Json much differently. Unfortunately its a new company (I have the sinking feeling that we are their first client to use their API). I could edit the json once returned to seperate totalrecords and below into something like {"CallData":{"total_records":"20","returned_count":"10"}} but that seems hacky – Andrew MacNaughton Oct 19 '14 at 19:38
  • You could hack the raw string before deserialising yes, but in this case you might want to contact the API provider and ask them to fix their code? – Stephen Kennedy Oct 19 '14 at 19:40
  • The JSON in your example is not valid. Please change 'returned_records: '1-10',to 'returned_records': '1-10', **note** the missing (') after returned_records. – Nripendra Oct 19 '14 at 19:57
  • I meant missing quote mark not missing curly bracket :) Anyway, looks like L.B. has pointed you to the answer. – Stephen Kennedy Oct 19 '14 at 20:07
  • @L.B can you please explain what part of the duplicate question has my answer??? I actually used that question as a reference along with JSON.Net to help me deserialize to a dictionary in the first place. – Andrew MacNaughton Oct 19 '14 at 20:56
  • @Nripendra thanks, just a typo whilst copying over. – Andrew MacNaughton Oct 19 '14 at 20:57
  • 1
    @L.B the question doesn't seem duplicate? The reference link you pointed is a case of homogeneous collection of key values, while here the question is collection of mixed key values. Where most of values are object of Product, and some metadata of basic types are there too. A preferable solution would be as Stephen kennedy suggested: to contact API provider and get them fixed the structure. But it may not always be possible, as there may be lots of consumer in wild whose code could break. – Nripendra Oct 20 '14 at 00:16

3 Answers3

1

First you json is not valid one, it should look like this

{
 "response":{  
    "6112":{  
        "ID":"6112",
        "Title":"Additional Photos",
    },
    "5982":{  
        "ID":"5982",
        "Title":"Bike Ride",
    },
    "total_records": "20",
    "returned_count": "10",
    "returned_records": "1-10",
    }
}

If you mean the response to contain list it should look like this

{
 "response":{  
    "myArray": [
        {  
            "ID":"6112",
            "Title":"Additional Photos",
        },
        {  
            "ID":"5982",
            "Title":"Bike Ride",
        }
    ],
    "total_records": "20",
    "returned_count": "10",
    "returned_records": "1-10",
    }
}

So your code look like this

public class MyArray
{
    public string ID { get; set; }
    public string Title { get; set; }
}

public class Response
{
    public List<MyArray> myArray { get; set; }
    public string total_records { get; set; }
    public string returned_count { get; set; }
    public string returned_records { get; set; }
}

public class RootObject
{
    public Response response { get; set; }
}
Mzf
  • 5,210
  • 2
  • 24
  • 37
  • Agreed that the JSON should look something like the second example (with Products as an array or dictionary) but the OP says that he does not have control over the data recieved from the API :/ – Stephen Kennedy Oct 19 '14 at 19:37
  • Thanks @Mzf. Stephen is correct. No access to that. – Andrew MacNaughton Oct 19 '14 at 19:39
  • @AndrewMacNaughton - for using API- if the response should be JSON but it's not valid you can't use the built-in way. if this were a valid Json - do you know how many id's going to return by the API ? can it change ? – Mzf Oct 19 '14 at 19:44
  • @Mzf. Yes product count can change at any time. I agree that if I was creating the json i would do it differently. I will push back to the software creator and ask whether they can change the JSON to something more user friendly. – Andrew MacNaughton Oct 19 '14 at 20:53
0

If you have control over API response then please refer to Mzf's answer.

If you don't have control over API then it may not be possible to do this particular deserialization on one go. You might have to loop.

Here's my take.

Update

Modified my approach:

Created a class Response which inherits from Dictionary<string, Product>, and added the metadata parts like total_records, records_count to it's public properties. And created a JsonConverter that can deserialize JObject to Response class.

The logic used for deserialization is quite simple:

  • Extract the metadata parts like total_records, records_count to variables.
  • Then remove those metadata from the JObject, so that the key values becomes homogeneous.
  • Now Json.net will be easily able to serialize JObject to Response object, as key values are homogenous.
  • Assign the metadata extracted previously to the properties of Response object

    public void Deserialize()
    {
        var json = @"{
          'response':{  
            '6112':{  
               'ID':6112,
               'Title':'Additional Photos',
            },
            '5982':{  
                'ID':5982,
                'Title':'Bike Ride',
            },
            'total_records': '20',
            'returned_count': 10,
            'returned_records': '1-10',
            }
        }";
        var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject<ss>(json, new ResponseConverter());
    
    }
    
    public class Response : Dictionary<string, Product>
    {
        public int total_records { get; set; }
    
        public int returned_count { get; set; }
    
        public string returned_records { get; set; }
    }
    
    public class Product
    {
        public string Id { get; set; }
        public string Title { get; set; }
    }
    
    
    public class ss
    {
        public Response Response { get; set; }
    }
    
    public class ResponseConverter : Newtonsoft.Json.JsonConverter
    {
        private Response CreateResponse(Newtonsoft.Json.Linq.JObject jObject)
        {
            //preserve metadata values into variables
            int total_records = jObject["total_records"].ToObject<int>();
            var returned_records = jObject["returned_records"].ToObject<string>();
            var returned_count = jObject["returned_count"].ToObject<int>();
    
            //remove the unwanted keys
            jObject.Remove("total_records");
            jObject.Remove("returned_records");
            jObject.Remove("returned_count");
    
            //once, the metadata keys are removed, json.net will be able to deserialize without problem
            var response = jObject.ToObject<Response>();
    
            //Assign back the metadata to response object
            response.total_records = total_records;
            response.returned_count = returned_count;
            response.returned_records = returned_records;
    
           //.. now person can be accessed like response['6112'], and
           // metadata can be accessed like response.total_records
    
            return response;
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Response);
        }
    
        public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
        {
            var jObject = Newtonsoft.Json.Linq.JObject.Load(reader);
    
            Response target = CreateResponse(jObject);
    
            serializer.Populate(jObject.CreateReader(), target);
    
            return target;
        }
    
        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
Nripendra
  • 359
  • 6
  • 12
-1

In my opinion this is how the JSON file should look like:

{
"response": {
    "5982": {
        "ID": 5982,
        "Title": "BikeRide"
    },
    "6112": {
        "ID": 6112,
        "Title": "AdditionalPhotos"
    },
    "total_records": "20",
    "returned_count": 10,
    "returned_records": "1-10"
}
}

and this is how the class should look like

public class __invalid_type__5982
{
 public int ID { get; set; }
 public string Title { get; set; }
}

public class __invalid_type__6112
{
  public int ID { get; set; }
  public string Title { get; set; }
}

public class Response
{
 public __invalid_type__5982 __invalid_name__5982 { get; set; }
 public __invalid_type__6112 __invalid_name__6112 { get; set; }
 public string total_records { get; set; }
 public int returned_count { get; set; }
 public string returned_records { get; set; }
}

public class RootObject
{
 public Response response { get; set; }
}
h__
  • 761
  • 3
  • 12
  • 41
  • 2
    I doubt the 5982 and 6112 are fixed, they're surely going to depend on which products he asks for? – Stephen Kennedy Oct 19 '14 at 19:35
  • 1
    Seems like you just copied and pasted from Json2Csharp. It is clear that the ids like `5982` can change any time. – L.B Oct 19 '14 at 19:36
  • Yes, right now we have about 300 products... – Andrew MacNaughton Oct 19 '14 at 19:41
  • @AndrewMacNaughton BTW: how about posting a **valid** json? – L.B Oct 19 '14 at 19:49
  • @L.B i simply posted the JSON that i am receiving from an API asking for assistance. If i had control over the JSON things would be different. – Andrew MacNaughton Oct 19 '14 at 20:54
  • @AndrewMacNaughton json you posted is **not valid**, if a side returns it, then it is the problem of that site. Just report it (post this link http://jsonlint.com/). But in your case, even if it were correct, you don't seem to be able to deserialize it correctly. So two problems at the same time. – L.B Oct 19 '14 at 20:58
  • Ok. I will discuss with the software provider. – Andrew MacNaughton Oct 19 '14 at 21:00