0

I am trying to deserialize a JSON object that has an array of unnamed arrays and am running into some problems. The code I am running to test:

var json = "{ \"Triangles\": [[1337],[1338],[1339]]}";
var mesh  = JsonConvert.DeserializeObject<Mesh>(json);

and the classes of interest:

public class Mesh
{
    [JsonProperty]
    public Triangle[] Triangles { get; set; }
}

public class Triangle
{
    [JsonProperty]
    public int[] Indices { get; set; }
}

Running the code and trying to deserialize using Newtonsoft I get the following exception:

Newtonsoft.Json.JsonSerializationException
  HResult=0x80131500
  Message=Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'ConsoleApp1.Triangle' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'triangles[0]', line 1, position 17.

Adding [JsonArray] to the Triangle class results in the following exception though:

Newtonsoft.Json.JsonSerializationException
  HResult=0x80131500
  Message=Cannot create and populate list type ConsoleApp1.Triangle. Path 'Triangles[0]', line 1, position 17.

What am I missing?

Edit: An important thing I obviously forgot to mention was that I would like to deserialize it into the classes listed in the post for semantic reasons. That is, although deserializing Triangles into List<List<int>> or int[][] would work I would very much prefer to not do so.

wazz
  • 4,953
  • 5
  • 20
  • 34
user1323245
  • 638
  • 1
  • 8
  • 20
  • The `Triangles` property in your Json is an array of arrays. You could fix it like this: `public List> Triangles { get; set; }` – DavidG Nov 04 '19 at 10:06
  • 2
    The result of the serialisation of your class will look like `{"Triangles":[{"Indices":[1,2]},{"Indices":[3]},{"Indices":[4]}]}`. You have to either deserialise to `List>` and select to the right class with a simple select or write a custom parser. – Drag and Drop Nov 04 '19 at 10:06
  • What you have there is an array of arrays of int. The serialized string should look more like `{"Triangles":[{"Indices":[1,2,3]},{"Indices":[1,2,3]},{"Indices":[1,2,3]}]}` – Matthew Watson Nov 04 '19 at 10:07
  • @DragandDrop I missed adding that I would prefer to keep the Triangle as a class for semantic reasons. I guess a custom parser is the way I would have to go then. – user1323245 Nov 04 '19 at 10:45
  • One class that represent the data as you got it. One class to represent the data as you will use. And a select between the two is what I recommend. The Custom parser may be hard. I was pretty sure I had one similar question with a parser in my notepad, but I can't find it. – Drag and Drop Nov 04 '19 at 10:52
  • "although deserializing Triangles into `List>` or `int[][]` would work I would very much prefer to not do so." - That _is_ how the data is offered from the Json though. The fact you don't like it doesn't change that. Any transformations you do on that data to pour it into your own classes are _post-processing steps_ unrelated to the deserialisation itself. By trying to make it part of the deserialisation you're just complicating things. – Nyerguds Nov 04 '19 at 10:55

2 Answers2

0

With a Custom converter. Deserialise the array to a JArray and select from there.

var json = "{ \"Triangles\": [[1337,1400],[1338],[1339]]}";
var mesh = JsonConvert.DeserializeObject<Mesh>(json);

public class Mesh
{
    [JsonConverter(typeof(MyConverter))]
    public Triangle[] Triangles { get; set; }
}   
public class Triangle
{
    [JsonProperty]
    public int[] Indices { get; set; }
}

public class MyConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }
        var result =
            JArray.Load(reader)
            .Select(x =>
                new Triangle { Indices = x.Select(y => (int)y).ToArray() }
            );

        return result.ToArray();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
Drag and Drop
  • 2,672
  • 3
  • 25
  • 37
-1

The way your classes are currently set up. JSON.NET will try to deserialize Triangle[] as an array of json object. The most simple solution is to change the type of Triangles to int[,] to make it a 2d array. If you want to keep using Triangle[] you need to use a custom JsonConverter.

Edit: Since you want to keep the Triangle class you need a custom JsonConverter class.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class TriangleJsonConverter : JsonConverter<Triangle>
{
    // Called when Triangle is written
    public override void WriteJson(JsonWriter writer, Triangle value, JsonSerializer serializer)
    {
        // Uses JsonSerializer to write the int[] to the JsonWriter
        serializer.Serialize(writer, value.Indices);
    }

    // Called when Triangle is read
    public override Triangle ReadJson(JsonReader reader, Type objectType, Triangle existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        // Reads a json array (JArray) from the JsonReader
        var array = JArray.Load(reader);
        // Creates a new Triangle.
        return new Triangle
        {
            // converts the json array to an int[]
            Indices = array.ToObject<int[]>()
        };
    }
}

To tell JSON.NET to use the TriangleJsonConverter you need to apply JaonArray to the Triangles field instead of JsonProperty.

public class Mesh
{
    [JsonArray(ItemConverterType = typeof(TriangleJsonConverter))]
    public Triangle[] Triangles { get; set; }
}
Rain336
  • 1,450
  • 14
  • 21