JSON Can't be Deserialized to Object, Needs Array?

15,163

Solution 1

Ok, you mention that genetics and price are arrays, but the returned JSON only contains one item. The rest of your RootObject are simple string properties.

Since you did not post a raw JSON example. Let me present a trimmed down version, to keep things simple and short.

{
    "url": "http://www.stackoverflow.com",
    "display": "This is a test",
    "genetics": [
        "typeophere"
    ],
    "price": [
        "$1400"
    ],
    "form": "a form"
}

Now we can trim down the RootObject class type. I used Pascal casing for my properties and attributed them with JSON.NET attributes to assist with the deserialing / serializing.

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class RootObject
{
    [JsonProperty(PropertyName = "url")]
    public string Url { get; set; }

    [JsonProperty(PropertyName = "display")]
    public string Display { get; set; }

    [JsonProperty(PropertyName = "genetics")]
    public List<string> Genetics { get; set; }

    [JsonProperty(PropertyName = "price")]
    public List<string> Price { get; set; }

    [JsonProperty(PropertyName = "form")]
    public string Form { get; set; }
}

Since I don't know the web service you are calling I just stored this JSON in a text file. Marked it as an embedded resource and read it from there.

string json;
var resource = Application.GetResourceStream(new Uri("json.txt", UriKind.Relative));
using (var reader = new StreamReader(resource.Stream))
{
    json = reader.ReadToEnd();
}

Just replace this part with your WebClient call to obtain your JSON data.

Now we can deserialize the JSON into a RootObject instance.

var rootObject = JsonConvert.DeserializeObject<RootObject>(json);

Works as advertised. Now you need to bind it to a ListBox control. If you hover over the ItemSource property of your ListBox instance you'll see that the tooltip mentions that you can bind it to a collection. Well a single rootObject isn't a collection. You can't bind it directly.

lstBoxResults.ItemsSource = rootObject;

This will NOT work. You cannot convert a RootObject instance to a System.Collections.IEnumerable. Which is what ItemsSource is expecting.

Easy fix. Let's create a collection.

lstBoxResults.ItemsSource = new List<RootObject> { rootObject };

This is assuming that your JSON data only returns one rootObject. Only one item will appear in your ListBox.

Now let's suppose your JSON data returns an array of RootObjects. For example an array called "items" which contains a collection of RootItems.

{
    "items": [
    {
    "url": "http://www.stackoverflow.com",
    "display": "This is a test",
    "genetics": [ "typeophere" ],
    "price": [ "$1400" ],
    "form": "a form"        
    },
    {
    "url": "https://github.com/geersch",
    "display": "This is another test",
    "genetics": [ "typeophere" ],
    "price": [ "$2000" ],
    "form": "another form"
    }]
}

Deserialing this is equally easy. Using JSON.NET obtain the collection of RootObjects.

var data = JObject.Parse(json)["items"];

Now deserialize into a collection (IEnumerable).

var rootObjects = JsonConvert.DeserializeObject<IEnumerable<RootObject>>(data.ToString());

Since you now already have a collection of RootObject there is no need to create one yourself. You can directly bind it to the ListBox.

lstBoxResults.ItemsSource = rootObjects;

Seems like the JSON data you receive is invalid. Just before parsing it make sure you modify it so that you have a valid JSON object.

For example:

json = json.Substring(json.IndexOf("[") + 1);
json = json.Substring(0, json.LastIndexOf("]"));
var rootObjects = JsonConvert.DeserializeObject<RootObject>(json);

Solution 2

Try this

RootObject rootObject;

if (json.startsWith("["))
{
  rootObject = JsonConvert.DeserializeObject<List<RootObject>>(json)[0];
}
else
{
  rootObject = JsonConvert.DeserializeObject<RootObject>(json);
}
Share:
15,163
user1239234
Author by

user1239234

Updated on June 04, 2022

Comments

  • user1239234
    user1239234 almost 2 years

    I am trying to take the incoming JSON items and bind them to listbox items but I am told by visual studio that I need to do an Array and not Object? I've never had to do this... Anyone know how?

    My RootObject:

    public class RootObject
    {
        public string url { get; set; }
        public string display { get; set; }
        public List<string> genetics { get; set; }
        public List<string> price { get; set; }
        public List<string> brandMaker { get; set; }
        public string form { get; set; }
        public string dosornos { get; set; }
        public string qty { get; set; }
        public string mfg { get; set; }
        public string mobURI { get; set; }
    }
    

    Note: Genetics, Price, BrandMaker don't actually return anything but a value, like below:

    "genetics": [
        "typeophere"
    ],
    "price": [
        "$1400"
    ],
    

    JSON FILE/REQUEST BASIC RESULT:

      [
    {
        "url": "N/A",
        "display": "",
        "genetics": [
            "microogiz"
        ],
        "price": [
            "96.016"
        ],
        "brandMaker": [
            "Oshi Kunti Multikashi, Osaka, JP"
        ],
        "form": "tangent",
        "dosornos": "n/a",
        "qty": "88G",
        "mfg": "SelfMade Industries, USA Dist.",
        "mobURI": "n/a"
    }
    

    ]

    My original code:

    // Get JSON via WEB
    string ProviderURI = goURI;
    webClient webClient = new WebClient();
    webClient.DownloadStringCompleted += new  
        DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
    webClient.DownloadStringAsync(new Uri(ProviderURI));
    
    void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error != null)
        {
            return;
        }
    
        var deserializedJSON = JsonConvert.DeserializeObject<RootObject>(e.Result);
        lstBoxResults.ItemsSource = deserializedJSON; // or deserializedJSON.url.ToString();
    }
    
  • user1239234
    user1239234 almost 12 years
    First, thanks so much for trying to help! I do have an issue though... After I request the JSON file and tell it to go to the DownloadStringCompleted I run: "var rootObject = JsonConvert.DeserializeObject<RootObject>(e.Result); pillResults.ItemsSource = new List<RootObject> { rootObject };" and even using your class it errors saying "Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'HealthCenter.MainPage+RootObject' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly. change the JSON to a JSON object (e.g. {"name":"value"}) change to array
  • Christophe Geers
    Christophe Geers almost 12 years
    Well, the JSON response that you show in your question is not valid. I get the same exception if I try to parse that. Remove the enclosing brackets [ ] and you'll have a valid JSON object that you can parse. It's just one RootObject though.
  • user1239234
    user1239234 almost 12 years
    Hi Christophe, unfortunately I don't think the company will let me change them just yet but I will submit for maybe a duplicate. It works on our iOS Device but odd WP7 can't do it. Thanks again for your assistance!
  • Christophe Geers
    Christophe Geers almost 12 years
    You receive the JSON data as a string. Just correct it before parsing it with the JSON.NET library. Not saying you should go and fix the web service which delivers it.
  • user1239234
    user1239234 almost 12 years
    This worked, unfortunately can't on the first if/check add the rootObject to a listboxes itemssource but that's ok. Only other issue, is if I am doing something with say, brandMaker and do rootObject.brandMaker I have to add .ToString(); but doesn't show, it shows System.Collections... Either way, does work for some of the items, awesome, thanks!