0

So in my app I have to get a JSON string. It can be a City or a List of Cities.

In the City class I have this:

public class City
{
    public string id { get; set; }
    public string country { get; set; }
    public string region { get; set; }
    public string city { get; set; }
    public string latitude { get; set; }
    public string longitude { get; set; }
    public string comment { get; set; }
    public bool wasThereAnError { get; set; }

    public class CityResponse
    {
        public string status { get; set; }
        public string message { get; set; }
        //public City result { get; set; }
        public List<City> result { get; set; }
    }

So it uses the List result to store data. This works fine when I get a JSON array back, it stores them all easily. However if I just query for 1 city, I get an exception about it needing an array. Here is the code for my call:

    async private Task<City.CityResponse> GetCityInformation(string url)
    {
        var client = new HttpClient();
        var response = await client.GetAsync(new Uri(url));
        string result = await response.Content.ReadAsStringAsync();
        var cityRoot = JsonConvert.DeserializeObject<City.CityResponse>(result);

        return cityRoot;
    }

Is it possible for me to store 1 city in the list too? Or do I need to make a seperate cities class or how do I go about this? Thanks

Ayohaych
  • 5,099
  • 7
  • 29
  • 51
  • 2
    See here: http://stackoverflow.com/questions/11126242/using-jsonconvert-deserializeobject-to-deserialize-json-to-a-c-sharp-poco-class, both the accepted and the next answer after it will give you the solution – Stephen Byrne Nov 14 '13 at 01:08
  • @StephenByrne I don't know if the link can help me though, it seems they are just telling him to make his thing an array by using square brackets, but I need to know can I take an array and a single city without the array – Ayohaych Nov 14 '13 at 01:37
  • 1
    I am not sure... If you add an extra property `City Result` (different spell) into CityResponse, would it be populated with a single value? So you can handle setter then – LINQ2Vodka Nov 14 '13 at 01:47
  • @jim Nah it doesn't work because it clashes with the List name which is also result – Ayohaych Nov 14 '13 at 01:58
  • 1
    @AndyOHart is it possible to override/extend default deserializer? Though i'd prefere to force datasource send correct types... – LINQ2Vodka Nov 14 '13 at 02:01
  • 1
    For the single city case, can you have the city formatted as an array with one element? It seems like the remote server is a little loose with its contract. – bmm6o Nov 14 '13 at 02:03
  • @bmm6o Could you give me an example of that please? I'm not sure I follow – Ayohaych Nov 14 '13 at 02:09
  • 1
    @AndyOHart he means the server that gives you json sends a single value instead of an array with 1 value, so you should do something with that server :) – LINQ2Vodka Nov 14 '13 at 02:12
  • @jim It's not my server I just have to make an application that uses the api – Ayohaych Nov 14 '13 at 02:15
  • 1
    @AndyOHart then serialize it to string and parse by yourself :) Good option, as for me. I would do like this. – LINQ2Vodka Nov 14 '13 at 02:17
  • @I'm not sure how I do that. Do I keep it in the same class or what? – Ayohaych Nov 14 '13 at 02:18
  • 1
    @AndyOHart you make `public string result { get; set; }` property instead of `List<>`. Then in setter you write code that determines if the string is a json object or json array of objects (examine for "{" maybe). After that you parse it depending on type into some other field `List<> GoodResult` and voila! – LINQ2Vodka Nov 14 '13 at 02:20
  • @Thanks jim ill give that a go! – Ayohaych Nov 14 '13 at 02:26
  • 1
    @AndyOHart jim's clarification of my comment is correct. Even if you don't have any influence on the server maintainers, you should let them know that this is weird and not a good API. Look at all the work it's causing you! Maybe it will prevent them from making this mistake in other places. – bmm6o Nov 14 '13 at 18:06
  • @bmm6o yeah I may mention it to them actually! thanks! – Ayohaych Nov 14 '13 at 19:09

2 Answers2

1

instead of:

public class CityResponse
    {
        public string status { get; set; }
        public string message { get; set; }
        public List<City> result { get; set; }
    }

try:

public class CityResponse
    {
        public string status { get; set; }
        public string message { get; set; }
        public string result { 
            get{ return null; }
            set{
                    // if 1st character is "[" then it's an array of City, otherwise a City object 
                    //depending on the above parse this string (which is like "{prop1: qqq, prop2: www}" 
                    // or like "[{prop1: qqq, prop2: www}, {prop1: eee, prop2: eee}]")
                    // by the existing serializer or other one 
                    // into City or array of cities
                    // if City, then convert in to array of cities
                    // and save result into realResult
            }
        }
        public List<City> realResult { get; set; }
LINQ2Vodka
  • 2,996
  • 2
  • 27
  • 47
1

Here's a small solution based on Jim's answer.

class CityResponse
{
    public string status { get; set; }

    public object result
    {
        get { return null; }
        set 
        {
            cities = new List<City>();

            if (value.GetType() == typeof(JArray))
            {
                cities = ((JArray)value).ToObject<List<City>>();
                foreach(var city in cities) city.ParentResponse = this; // Edit
                return;
            }

            if (value.GetType() != typeof(JObject)) 
                return;

            cities.Add(((JObject)value).ToObject<City>());
            foreach(var city in cities) city.ParentResponse = this; // Edit
        }
    }

    public string message { get; set; }

    public List<City> cities { get; internal set; }
}

Hope it helps!

PS: I don't know if the system that provides the JSON data is created by you, but having a member with an inconsistent type is bad design.

-- Edit --

In reply to a comment on this answer, asking on how to access the CityResponse from a City object, here's how I would do it:

I would add a new property to the City class, which would be meant for holding the parent CityResponse

public class City
{
    public string id { get; set; }

    ...

    public CityResponse ParentResponse { get; set;}
}

And then perform some minor changes to the setter, as seen in the original portion of the answer above.

OrfeasZ
  • 71
  • 3
  • Thanks for the reply. I tried to change the List name to cities like you have, however when I do a call to the API, it returns the list empty, it seems like it has to be called result :S – Ayohaych Nov 14 '13 at 12:40
  • 1
    @AndyOHart The `cities` property is internally set when the JSON deserializer tries to set the `result` property (you'll notice that the setter for that property is explicitly defined). It is not actually expected to be present in the JSON object you receive. – OrfeasZ Nov 14 '13 at 12:52
  • What would be the best way for me to print out the city? Originally I had a method that overrided ToString() in the City class, however now since it is always stored in a list, I'm not sure how I should do it – Ayohaych Nov 14 '13 at 13:06
  • 1
    @AndyOHart I would re-implement your `ToString()` override and then iterate through the results (`cities`) using a `for` or `foreach` loop, while printing them out. – OrfeasZ Nov 14 '13 at 13:19
  • How do I access the List cities?? When I try to use it in the tostring I can't access that variable? – Ayohaych Nov 14 '13 at 13:25
  • 1
    @AndyOHart You can access it from the result of your `GetCityInformation()` call. – OrfeasZ Nov 14 '13 at 13:31
  • So I can't do it inside of the City class? – Ayohaych Nov 14 '13 at 13:46
  • 1
    @AndyOHart No. In order to do so you would have to add a reference to the parent `CityResponse` class in a new property, preferably in the `result` setter. – OrfeasZ Nov 14 '13 at 13:53
  • Could you possibly give me an example of how I can do this as I don't really get what you mean sorry :S – Ayohaych Nov 14 '13 at 13:56
  • 1
    @AndyOHart I edited the initial answer to contain more information. I should however note that accessing the rest of the City object from another City object for such a purpose is probably bad practice. – OrfeasZ Nov 14 '13 at 14:11
  • I only want to be able to access the id and all that information basically to print out to the screen :) I Will try your solution now thank you – Ayohaych Nov 14 '13 at 14:35
  • Should I just add the code you had in the original response to the CityResponse ParentResonse set part? – Ayohaych Nov 14 '13 at 14:37