James Wilson James Wilson - 6 months ago 50
JSON Question

C# Json.Net: {"Unable to cast object of type 'System.Boolean[,]' to type 'System.Collections.Generic.ICollection`1[System.Boolean]'."}

I'm trying to deserialise a complex object, which can contain instances of that same class (WorldInstance, which can contain more WorldInstances), but it's throwing this exception and I have no idea why, or how to fix it.

I know this is a bit vague, but I have no idea what information is needed, so I will post anything that is asked of me.

EDIT 1: Here's the entire class I'm trying to serialise.
http://pastebin.com/QTNH1HZH

I think it's failing on the discoveredTiles, on line 691, even though it shouldn't be doing anything with that data.

dbc dbc
Answer

Your problem is that you are trying to make Json.NET deserialize a get-only array property discoveredTiles, as is shown in the following simplified version of your class:

public class WorldInstance
{
    protected WorldTile[,] m_Tiles;
    protected bool[,] m_Discovered;

    public WorldInstance(WorldTile[,] tiles)
    {
        m_Tiles = tiles;
        m_Discovered = new bool[m_Tiles.GetLength(0), m_Tiles.GetLength(1)];
    }

    public WorldTile[,] tiles
    {
        get
        {
            return m_Tiles;
        }
    }

    public bool[,] discoveredTiles
    {
        get
        {
            return m_Discovered;
        }
    }
}

public class WorldTile
{
}

This is currently not implemented even if the array is pre-allocated and has the same length as the JSON array, since Json.NET will try to add deserialized items to the pre-allocated array, which is of course impossible. (Note that the tiles property will get deserialized by passing it to the constructor, matching the JSON property name to the constructor argument name.)

For a 1d array property, the exception thrown explains this problem:

System.NotSupportedException: Collection was of a fixed size.
   at System.SZArrayHelper.Add[T](T value)
   at Newtonsoft.Json.Utilities.CollectionWrapper`1.Add(T item) in C:\Development\Releases\Json\Working\Newtonsoft.Json\Working-Signed\Src\Newtonsoft.Json\Utilities\CollectionWrapper.cs:line 76
   at Newtonsoft.Json.Utilities.CollectionWrapper`1.System.Collections.IList.Add(Object value) in C:\Development\Releases\Json\Working\Newtonsoft.Json\Working-Signed\Src\Newtonsoft.Json\Utilities\CollectionWrapper.cs:line 194
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id) in C:\Development\Releases\Json\Working\Newtonsoft.Json\Working-Signed\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:line 2030

But for a 2d array the less illuminating InvalidCastException is thrown.

You have two workarounds for this problem:

  1. Add a protected constructor to WorldInstance that includes a bool[,] discoveredTiles argument and mark it with [JsonConstructor], for instance:

    [JsonConstructor]
    protected WorldInstance(WorldTile[,] tiles, bool[,] discoveredTiles)
    {
        if (tiles == null || discoveredTiles == null)
            throw new ArgumentNullException();
        // Ensure the tiles and discoveredTiles arrays have the same sizes
        if (tiles.GetLength(0) != discoveredTiles.GetLength(0) || tiles.GetLength(1) != discoveredTiles.GetLength(1))
            throw new InvalidOperationException("tiles.GetLength(0) != discoveredTiles.GetLength(0) || tiles.GetLength(1) != discoveredTiles.GetLength(1)");
        m_Tiles = tiles;
        m_Discovered = discoveredTiles;
    }
    

    Note that the parameter name discoveredTiles must be the same as the corresponding serialized property name.

  2. Add a private or protected setter for discoveredTiles and mark it with [JsonProperty]:

    [JsonProperty]
    public bool[,] discoveredTiles
    {
        get
        {
            return m_Discovered;
        }
        protected set
        {
            if (value == null)
                throw new ArgumentNullException();
            // Ensure the tiles and discoveredTiles arrays have the same sizes
            if (tiles != null && tiles.GetLength(0) != value.GetLength(0) || tiles.GetLength(1) != value.GetLength(1))
                throw new InvalidOperationException("tiles != null && tiles.GetLength(0) != value.GetLength(0) || tiles.GetLength(1) != value.GetLength(1)");
            m_Discovered = value;
        }
    }