简体   繁体   中英

Cannot deserialize the current json object into ObservableCollection

(Full error message at the bottom of the post)

I am aware that this has already been discussed in several other posts, however, the fixes provided in those posts seems to not work for me.

I am providing all my code below. The deserialization is in the method parseSpellData() .

sample JSON:

{
"_id": "58c9eb75c9e7ce9f7214efaa",
"index": 1,
"name": "Acid Arrow",
"desc": [
    "A shimmering green arrow streaks toward a target within range and bursts in a spray of acid. Make a ranged spell attack against the target. On a hit, the target takes 4d4 acid damage immediately and 2d4 acid damage at the end of its next turn. On a miss, the arrow splashes the target with acid for half as much of the initial damage and no damage at the end of its next turn."
],
"higher_level": [
    "When you cast this spell using a spell slot of 3rd level or higher, the damage (both initial and later) increases by 1d4 for each slot level above 2nd."
],
"page": "phb 259",
"range": "90 feet",
"components": [
    "V",
    "S",
    "M"
],
"material": "Powdered rhubarb leaf and an adder’s stomach.",
"ritual": "no",
"duration": "Instantaneous",
"concentration": "no",
"casting_time": "1 action",
"level": 2,
"school": {
    "url": "http://dnd5eapi.co/api/magic-schools/5",
    "name": "Evocation"
},
"classes": [
    {
        "name": "Wizard",
        "url": "http://dnd5eapi.co/api/classes/12"
    }
],
"subclasses": [
    {
        "url": "http://dnd5eapi.co/api/subclasses/2",
        "name": "Lore"
    },
    {
        "url": "http://dnd5eapi.co/api/subclasses/4",
        "name": "Land"
    }
],
"url": "http://dnd5eapi.co/api/spells/1"
}

Code:

class MainPageViewModel
{
    public ObservableCollection<Spell> availableSpells { get; set; } = new ObservableCollection<Spell>();


    public MainPageViewModel()
    {
        availableSpells = parseSpellData(getSpells());
    }


    public string getSpells()
    {
        string spells = string.Empty;

        try
        {
            HttpWebRequest request = WebRequest.Create("http://dnd5eapi.co/api/Spells") as HttpWebRequest;

            using (var response = request.GetResponse())
            {
                using (var reader = new StreamReader(response.GetResponseStream()))
                {
                    spells = reader.ReadToEnd();
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
        Console.WriteLine(spells);
        return spells;
    }

    public ObservableCollection<Spell> parseSpellData(string listOfSpells)
    {
        ObservableCollection<Spell> parsedSpellData = JsonConvert.DeserializeObject<ObservableCollection<Spell>>(listOfSpells); 

        return parsedSpellData as ObservableCollection<Spell>;
    }        
}

public class School
{
    public string url { get; set; }
    public string name { get; set; }
}

public class Class
{
    public string name { get; set; }
    public string url { get; set; }
}

public class Subclass
{
    public string url { get; set; }
    public string name { get; set; }
}

public class Spell
{
    public string _id { get; set; }
    public int index { get; set; }
    public string name { get; set; }
    public List<string> desc { get; set; }
    public List<string> higher_level { get; set; }
    public string page { get; set; }
    public string range { get; set; }
    public List<string> components { get; set; }
    public string material { get; set; }
    public string ritual { get; set; }
    public string duration { get; set; }
    public string concentration { get; set; }
    public string casting_time { get; set; }
    public int level { get; set; }
    public School school { get; set; }
    public List<Class> classes { get; set; }
    public List<Subclass> subclasses { get; set; }
    public string url { get; set; }
}

PM Vizualization Error

Cannot deserialize the current JSON object (eg{"name":"value"}) into type 'System.Collections.ObjectModel.ObservableCollection´1[NInterpret.interpretedObject]' because the type requires a JSON array (eg [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (eg [1,2,3]) or change the deserialized type so that it is a normal .NET type (eg. not a primitive type like integer, not a collection type likan an array or List) that can be deserialized from a JSON object.

The problem is that your JSON is not an array, it's an object containing an array-valued property. You can see this more clearly if you format the JSON returned by http://dnd5eapi.co/api/Spells using https://jsonformatter.curiousconcept.com/ :

{
   "count":305,
   "results":[
      {
         "name":"Acid Arrow",
         "url":"http://www.dnd5eapi.co/api/spells/1"
      },
      {
         "name":"Acid Splash",
         "url":"http://www.dnd5eapi.co/api/spells/2"
      }
      // Additional entries omitted
   ]
}

Thus you need to deserialize to a model that contains a "results" collection, rather than to the collection itself. One easy way to do this is to use JsonConvert.DeserializeAnonymousType() to specify the wrapper root object:

public ObservableCollection<Spell> parseSpellData(string listOfSpells)
{
    var parsedSpellData = JsonConvert.DeserializeAnonymousType(listOfSpells, new { results = (ObservableCollection<Spell>)null });

    return parsedSpellData.results;
}   

Working fiddle #1 .

Of course you could specify an explicit type for the root as well -- in fact your MainPageViewModel could be used for this purpose if you add [JsonProperty("results")] to availableSpells :

class MainPageViewModel
{
    [JsonProperty("results")]
    public ObservableCollection<Spell> availableSpells { get; set; } = new ObservableCollection<Spell>();

    public MainPageViewModel()
    {
        JsonConvert.PopulateObject(getSpells(), this);
    }

Sample working fiddle #2 .

Actually, your data model seems to have many properties not present in the JSON, where the returned objects in the returned "results" array contain only "name" and "url" properties. Are you sure you are using the correct data model for this particular API?

Update

It looks like the JSON posted in your question is returned by the specific spell API URLs listed in the returned results, eg http://www.dnd5eapi.co/api/spells/1 , which furthermore corresponds to your Spell type. If you're looking to fetch all that spell data at once, you could introduce the following types

public class NameAndUri
{
    public string url { get; set; }
    public string name { get; set; }
}

public class NameAndUri<T> : NameAndUri
{
    public T Deserialize()
    {
        return JsonExtensions.DeserializeFromUri<T>(url);
    }
}

public static partial class JsonExtensions
{
    public static T DeserializeAnonymousTypeFromUri<T>(string uri, T anonymousTypeObject)
    {
        return DeserializeFromUri<T>(uri);
    }

    public static T DeserializeFromUri<T>(string uri)
    {
        HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
        using (var response = request.GetResponse())
        using (var reader = new StreamReader(response.GetResponseStream()))
        using (var jsonReader = new JsonTextReader(reader))
        {
            return JsonSerializer.CreateDefault().Deserialize<T>(jsonReader);
        }
    }
}

And modify MainPageViewModel as follows:

class MainPageViewModel
{
    public const string spellUri = "http://dnd5eapi.co/api/Spells";

    public ObservableCollection<Spell> availableSpells { get; set; } // = new ObservableCollection<Spell>();

    public MainPageViewModel()
    {
        availableSpells = parseSpellData(spellUri);
    }

    static ObservableCollection<Spell> parseSpellData(string spellUri)
    {
        var parsedSpellData = JsonExtensions.DeserializeAnonymousTypeFromUri(spellUri, new { results = (List<NameAndUri<Spell>>)null });            
        var query = parsedSpellData.results.Select(s => s.Deserialize());
        return new ObservableCollection<Spell>(query);
    }   
}

Notes:

  • As recommended in Newtonsoft: Performance Tips I am deserializing directly from the response streams rather than loading into an intermediate string .

  • You might want to replace the School , Class and Subclass types with NameAndUri<T> for some appropriate T .

  • If the performance of loading all the spells at once is not good enough, consider moving to an async solution .

Sample fiddle #3 which times out on https://dotnetfiddle.net/ but works locally.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM