简体   繁体   中英

Convert JObject to multi-dimensional array

How do you convert a JObject to a 3D array? I have a JObject that is formatted as such:

{
  "0": [
    [
      1.0,
      2.0,
      3.0
    ],
    [
      4.0,
      5.0,
      6.0
    ]
  ],
  "1": [
    [
      7.0,
      8.0,
      9.0
    ],
    [
      10.0,
      11.0,
      12.0
    ]
  ]
}

I've tried casting it to a double[,,] but it fails with an error stating

Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'System.Double[,,]'.

The following works for me:

var deserailizationExp = JsonConvert.DeserializeObject<Dictionary<string, double[,]>>(@"
          {""0"": [
            [
              1.0,
              2.0,
              3.0
            ],
            [
              4.0,
              5.0,
              6.0
            ]
          ],
          ""1"": [
            [
              7.0,
              8.0,
              9.0
            ],
            [
              10.0,
              11.0,
              12.0
            ]
          ]
        }");

You could then either use the dictionary directly or transform it to an array. Edit: As pointed out in the comments on this, you could also consider deserializing this to the type SortedDictionary<int, double[,]> . I tested that as a type and it worked for me.

Alternatively, if you modify the JSON you can do the following:

var otherDes = JsonConvert.DeserializeObject<double[,,]>(@"
          [[
            [
              1.0,
              2.0,
              3.0
            ],
            [
              4.0,
              5.0,
              6.0
            ]
          ],
          [
            [
              7.0,
              8.0,
              9.0
            ],
            [
              10.0,
              11.0,
              12.0
            ]
          ]
        ]");

As you can see, I just removed "0" and "1" and replaced {} with []. If you have the ability to control how you're receiving the JSON somehow this would probably be the better solution in my opinion since it matches your requested type without having to do any further operations on it.

Json.NET expects a multidimensional array to be formatted like a 3d jagged array in the JSON file, however yours is formatted like a dictionary of 2d jagged arrays. You can use a custom JsonConverter to convert JSON in such a format to a 3d array, like so:

public class Array3DConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        if (!objectType.IsArray)
            return false;
        return objectType.GetArrayRank() == 3;
    }

    object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType == JsonToken.StartArray)
        {
            // Handle case when it's actually a 3d array in the JSON.
            var list = serializer.Deserialize<List<List<List<T>>>>(reader);
            return list.Select((l, i) => new KeyValuePair<int, List<List<T>>>(i, l)).To3DArray();
        }
        else if (reader.TokenType == JsonToken.StartObject)
        {
            // Handle case when it's a dictionary of key/value pairs.
            var dictionary = serializer.Deserialize<SortedDictionary<int, List<List<T>>>>(reader);
            return dictionary.To3DArray();
        }
        else
        {
            throw new JsonSerializationException("Invalid reader.TokenType " + reader.TokenType);
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        try
        {
            var elementType = objectType.GetElementType();
            var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
            return method.MakeGenericMethod(new[] { elementType }).Invoke(this, new object[] { reader, objectType, existingValue, serializer });
        }
        catch (TargetInvocationException ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializerException
            throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
        }
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public static class EnumerableExtensions
{
    public static T[,,] To3DArray<T>(this IEnumerable<KeyValuePair<int, List<List<T>>>> jaggedArray)
    {
        if (jaggedArray == null)
            throw new ArgumentNullException("jaggedArray");
        var counts = new int[3];
        foreach (var pair in jaggedArray)
        {
            var i = pair.Key;
            counts[0] = Math.Max(i + 1, counts[0]);
            if (pair.Value == null)
                continue;
            var jCount = pair.Value.Count;
            counts[1] = Math.Max(jCount, counts[1]);
            for (int j = 0; j < jCount; j++)
            {
                if (pair.Value[j] == null)
                    continue;
                var kCount = pair.Value[j].Count;
                counts[2] = Math.Max(kCount, counts[2]);
            }
        }
        var array = new T[counts[0], counts[1], counts[2]];
        foreach (var pair in jaggedArray)
        {
            var i = pair.Key;
            if (pair.Value == null)
                continue;
            var jCount = pair.Value.Count;
            for (int j = 0; j < jCount; j++)
            {
                if (pair.Value[j] == null)
                    continue;
                var kCount = pair.Value[j].Count;
                for (int k = 0; k < kCount; k++)
                    array[i, j, k] = pair.Value[j][k];
            }
        }
        return array;
    }
}

Then use it like:

var array = JsonConvert.DeserializeObject<double[, ,]>(jsonString, new JsonSerializerSettings { Converters = new[] { new Array3DConverter() } });

Or, if you have already parsed your JSON string into a JObject , you can use JToken.ToObject<T>(JsonSerializer) to deserialize to your desired type using your converter:

var array = jObj.ToObject<double[, ,]>(JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = new[] { new Array3DConverter() } }));

For flexibility in use, the converter tests to see whether the incoming JSON is formatted as an object or array and responds appropriately.

Note - only lightly tested.

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