2
votes

I am having difficulty deserializing a JSON string sent from a javascript to my ASP.NET 4.52 C# WEB API for storing into a database. The JSON string originated from a multidimensional JavaScript array for a Battleship game I am writing. The data represents cell positions for ship on an x/y coordinate grid. In my array/string I have 5 ships and for each ship I have a varying number of x/y positions (in the form of name-value pairs example: row 3; column 3) The string below is the data received by my C# WebAPI and represents the multiple dimension JavaScript array data that is being sent via an ajax HTTPPOST

ShipAndPositions (my json string):

var ShipAndPositions = "[\"ship5\",[{\"row\":4,\"col\":6},{\"row\":5,\"col\":6},{\"row\":6,\"col\":6}],\"ship4\",[{\"row\":3,\"col\":8},{\"row\":3,\"col\":9},{\"row\":3,\"col\":10},{\"row\":3,\"col\":11}],\"ship3\",[{\"row\":8,\"col\":2},{\"row\":8,\"col\":3},{\"row\":8,\"col\":4},{\"row\":8,\"col\":5}],\"ship2\",[{\"row\":9,\"col\":7},{\"row\":9,\"col\":8},{\"row\":9,\"col\":9},{\"row\":9,\"col\":10},{\"row\":9,\"col\":11}],\"ship1\",[{\"row\":0,\"col\":0},{\"row\":0,\"col\":1},{\"row\":0,\"col\":2},{\"row\":0,\"col\":3},{\"row\":0,\"col\":4},{\"row\":0,\"col\":5}],\"ship0\",[{\"row\":12,\"col\":5},{\"row\":12,\"col\":6},{\"row\":12,\"col\":7},{\"row\":12,\"col\":8},{\"row\":12,\"col\":9},{\"row\":13,\"col\":5},{\"row\":13,\"col\":6},{\"row\":13,\"col\":7},{\"row\":13,\"col\":8},{\"row\":13,\"col\":9}]]"

This string is valid JSON according to jsonlint.com

I have defined my class as:

public class CoordPoints
{
    public string row { get; set; }

    public string col { get; set; }
}

public class CoordPointsArray
{
    public string ship { get; set; }

    //public List<CoordPoints> ShipPositions { get; set; }
    public Dictionary<string, CoordPoints[]> CoordPoints { get; private set; }
}

I have the newtonsoft.json.dll 4.5.8 installed I have tried all of the following and they ALL throw exceptions

1) DataContractJsonSerializer jsonObjectCoordsInfo = new DataContractJsonSerializer(typeof(CoordPointsArray)); MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(data.ShipAndPositions)); CoordPointsArray test1 (CoordPointsArray)jsonObjectCoordsInfo.ReadObject(stream);

2) var shipPositionList = serializer.Deserialize(data.ShipAndPositions);

3) var test3 = JsonConvert.DeserializeObject(data.ShipAndPositions);

4) CoordPointsArray rootObject = new CoordPointsArray(); rootObject = JsonConvert.DeserializeObject(data. ShipAndPositions);

I have tried many other variations. Can someone help me solve this problem.

1
Argh. Could you post the actual JSON for us? I thought about formatting the JSON properly, but man, I'm already getting a toothache thinking about fixing it. BTW, the reason for my ask, is, because I think your JSON is incorrect. Once you post the formatted JSON (remove the escapes, please), I'll show you what it is.code4life

1 Answers

0
votes

What you have here is not a multidimensional array. Instead, what you have is a collection of properties of a collection of objects presented as a flat list. If I format your JSON string for readability, it looks like:

[
  "ship5",
  [{"row":4,"col":6},{"row":5,"col":6},{"row":6,"col":6}],
  "ship4",
  [{"row":3,"col":8},{"row":3,"col":9},{"row":3,"col":10},{"row":3,"col":11}],
  "ship3",
  [{"row":8,"col":2},{"row":8,"col":3},{"row":8,"col":4},{"row":8,"col":5}],
  "ship2",
  [{"row":9,"col":7},{"row":9,"col":8},{"row":9,"col":9},{"row":9,"col":10},{"row":9,"col":11}],
  "ship1",
  [{"row":0,"col":0},{"row":0,"col":1},{"row":0,"col":2},{"row":0,"col":3},{"row":0,"col":4},{"row":0,"col":5}],
  "ship0",
  [{"row":12,"col":5},{"row":12,"col":6},{"row":12,"col":7},{"row":12,"col":8},{"row":12,"col":9},{"row":13,"col":5},{"row":13,"col":6},{"row":13,"col":7},{"row":13,"col":8},{"row":13,"col":9}]
]

With proper formatting, we can now see this fits the following pattern:

[
    Object1Property1Value,
    Object1Property2Value,

    Object2Property1Value,
    Object2Property2Value,

    Object3Property1Value,
    Object3Property2Value,
]

What you would like to do is to deserialize this flat list of properties to a List<T> for some type T that has the properties corresponding to the values for a single portion of the flat list above.

Firstly, I would say that this is not a particularly good way to represent your data. The structure of the JSON should reflect the structure of the data, which is not true in this case. A better format would be as a list of JSON objects, or at worst as an array of arrays of values of each object (in which case you could use Json.NET to deserialize your JSON, making use of ObjectToArrayConverter<CoordPointsArray> from Parsing JSON into C# Object - Get Properties Dynamically.)

That being said, if you cannot change the JSON format, this JSON can be deserialized successfully using Json.NET via a custom JsonConverter. First, define your data model as follows:

public class CoordPoint
{
    public int row { get; set; }
    public int col { get; set; }
}

public class CoordPointsArray
{
    [JsonProperty(Order = 1)]
    public string Name { get; set; }

    [JsonProperty(Order = 2)]
    public List<CoordPoint> Coordinates { get; set; }
}

Notice the [JsonProperty(Order = X)] attributes? Those will indicate to the custom converter the order in which the property values will appear in the JSON.

Next, define the following converter:

public class ObjectListToSequentialPropertyArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType.GetListType();
    }

    static bool ShouldSkip(JsonProperty property)
    {
        return property.Ignored || !property.Readable || !property.Writable;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var objectType = value.GetType();
        var itemType = objectType.GetListType();
        if (itemType == null)
            throw new ArgumentException(objectType.ToString());
        var itemContract = serializer.ContractResolver.ResolveContract(itemType) as JsonObjectContract;
        if (itemContract == null)
            throw new JsonSerializationException("invalid type " + objectType.FullName);
        var list = (IList)value;
        var propertyList = list
            .OfType<Object>()
            .Where(i => i != null) // Or should we throw an exception?
            .SelectMany(i => itemContract.Properties.Where(p => !ShouldSkip(p)).Select(p => p.ValueProvider.GetValue(i)));
        serializer.Serialize(writer, propertyList);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var itemType = objectType.GetListType();
        if (itemType == null)
            throw new ArgumentException(objectType.ToString());
        if (reader.TokenType == JsonToken.Null)
            return null;
        var array = JArray.Load(reader);
        var listContract = serializer.ContractResolver.ResolveContract(objectType) as JsonArrayContract;
        var itemContract = serializer.ContractResolver.ResolveContract(itemType) as JsonObjectContract;
        if (itemContract == null || listContract == null)
            throw new JsonSerializationException("invalid type " + objectType.FullName);
        var list = existingValue as IList ?? (IList)listContract.DefaultCreator();
        var properties = itemContract.Properties.Where(p => !ShouldSkip(p)).ToArray();

        for (int startIndex = 0; startIndex < array.Count; startIndex += properties.Length)
        {
            var item = itemContract.DefaultCreator();
            for (int iProperty = 0; iProperty < properties.Length; iProperty++)
            {
                if (startIndex + iProperty >= array.Count)
                    break;
                var propertyValue = array[startIndex + iProperty].ToObject(properties[iProperty].PropertyType, serializer);
                properties[iProperty].ValueProvider.SetValue(item, propertyValue);
            }
            list.Add(item);
        }
        return list;
    }
}

public static class TypeExtensions
{
    public static Type GetListType(this Type type)
    {
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

Finally, deserialize your JSON as follows:

var settings = new JsonSerializerSettings
{
    Converters = { new ObjectListToSequentialPropertyArrayConverter<CoordPointsArray>() },
};

var root = JsonConvert.DeserializeObject<List<CoordPointsArray>>(json, settings);

Working .Net fiddle showing deserialization and re-serialization to this format.

Note I have tested this with Json.NET versions 10 (the current version) and 6 (on https://dotnetfiddle.net/) but not 4.5.8.