Zack F Zack F - 6 months ago 181
JSON Question

Json.Net - Deserialize a list of objects which contain a list of objects

I'm attempting to deserialize json that describes the following:

A list of objects of type Item, each Item contains some properties as well as a list 'recipe' of objects of type Effect which contain three properties of their own (action, value, and target).

When I serialize my list using 'JsonConvert.SerializeObject' I get the following json:

[
{
"name": "WOOD",
"yield": 1.0,
"recipe": [
{
"action": "ADD",
"value": 1.0,
"target": "WOOD"
}
],
"count": 0.0,
"numWorkers": 0,
"id": 1
},
{
"name": "CLAY",
"yield": 2.0,
"recipe": [
{
"action": "ADD",
"value": 2.0,
"target": "CLAY"
}
],
"count": 0.0,
"numWorkers": 0,
"id": 2
},
{
"name": "SPEAR",
"yield": 0.5,
"recipe": [
{
"action": "ADD",
"value": 0.5,
"target": "SPEAR"
},
{
"action": "SUB",
"value": 1.0,
"target": "WOOD"
},
{
"action": "SUB",
"value": 5.0,
"target": "CLAY"
}
],
"count": 0.0,
"numWorkers": 0,
"id": 3
},
{
"name": "STICK",
"yield": 4.0,
"recipe": [
{
"action": "ADD",
"value": 4.0,
"target": "STICK"
},
{
"action": "SUB",
"value": 1.0,
"target": "WOOD"
}
],
"count": 0.0,
"numWorkers": 0,
"id": 4
}
]


But when I attempt to deserialize using '
Items = JsonConvert.DeserializeObject<List<Item>>(jsonstring);
' I get this error:
A first chance exception of type 'System.NullReferenceException' occurred in Newtonsoft.Json.dll
and my 'Items' List is null.

When I use json2csharp to generate the c# I get the following:

public class Recipe
{
public string action { get; set; }
public double value { get; set; }
public string target { get; set; }
}

public class RootObject
{
public string name { get; set; }
public double yield { get; set; }
public List<Recipe> recipe { get; set; }
public double count { get; set; }
public int numWorkers { get; set; }
public int id { get; set; }
}


It thinks my Item object is 'RootObject' and it's giving me a 'Recipe' object instead of a list of 'Effect' objects in the list 'recipe'

Here's some code for my game and the classes so you can see what I'm working with:

public List<Item> Items;

private void Game_Load(object sender, EventArgs e)
{
Items = JsonConvert.DeserializeObject<List<Item>>(jsonstring);
}

public class Item
{
public string name;
public double yield;
public List<Effect> recipe = new List<Effect>();
public double count;
public int numWorkers;
public int id;

public Item()
{
name = "";
//configureItem();
}

public Item(string nm)
{
name = nm.ToUpper();
//configureItem();
}

public List<Effect> getRecipe() {
return recipe;
}
}

public class Effect
{
public string action;
public double value;
public string target;

public Effect(string act, double val, string tar)
{
action = act.ToUpper();
value = val;
target = tar.ToUpper();
}
}


Do I need to have the
{ get; set; }
for all variables in my classes? I tried to add that before, but it seemed to be causing my VS to skip lines during debugging and all sorts of other weirdness. Or is it just a Json formatting issue? Any help would be appreciated, I've looked all over this site and Google in general and I'm ripping my hair out over here.

Answer

Your Event class does not have a default (parameterless) constructor, so Json.Net is trying to use the constructor it has. However, the parameter names do not match with anything in the JSON (there are no act, val or tar properties present). In this case, Json.Net will pass default values (i.e. null) for the parameters in order to get the object constructed, then go back and attempt to set the fields. The problem is your constructor is not equipped to handle null values. It assumes that act and tar will not be null when it calls ToUpper(). This is where the NullReferenceException is coming from.

One solution is to rename your constructor parameters to match the JSON (I would also add the appropriate null checks for safety):

public Effect(string action, double value, string target)
{
    this.action = (action != null ? action.ToUpper() : null);
    this.value = value;
    this.target = (target != null ? target.ToUpper() : null);
}

Another possible solution is to move the ToUpper logic into properties and provide a default constructor.

public class Effect
{
    private string _action;
    public string Action
    {
        get { return _action; }
        set { _action = (value != null ? value.ToUpper() : null); }
    }

    public double Value { get; set; }

    private string _target;
    public string Target
    {
        get { return _target; }
        set { _target = (value != null ? value.ToUpper() : null); }
    }

    public Effect()
    {
    }
}
Comments