2

I have the following JSON chunk (extracted from the full JSON in order to simplify the question):

 "statistics": [
  {
    "Strength": {
      "min": 16,
      "max": 20
    }
  },
  {
    "Dexterity": {
      "min": 16,
      "max": 20
    }
  }]

I'd like to deserialize the "statistics" array into a C# array of "Statistic" objects, but I can"t find how to do it... The key of each statistic object can be anything : "Strength","Luck","Dexterity" and so on, and each of those keys are unique.

The C# data object would be like this :

public class Container
{
    public Statistic[] Statistics { get; set; }
}

public class Statistic
{
    //Contains the name of the statistic
    public string Name { get; set; }
    public int Min { get; set; }
    public int Max { get; set; }
}

Maybe can I use some kind of polymorphism by removing the Name property and creating all possible classes of statistics, but it deafeats the adaptability of my code.

Thank you a lot.

9
  • Your JSON format is consistent with a Dictionary<string, Statistic> rather than a list. See e.g. Deserializing JSON when key values are unknown. But are the Name properties guaranteed to be unique? Commented Apr 29, 2020 at 15:48
  • They are, I add this remark in my question. I read the linked article and come back to you, thanks. Commented Apr 29, 2020 at 15:49
  • Is your data model fixed? The arrays doesn't seem the best choice here. Commented Apr 29, 2020 at 15:53
  • @dbc, I'm not exactly an expert in JSON, but I think the structure in the article you mentionned is different. They do have a JSON dictonary with key values pairs, what I have is an array of objects. Maybe some settings allow to deserialize my array into a dictionary, but I don't know how to do it. Commented Apr 29, 2020 at 15:55
  • 1
    @dbc Actually, the JSON represents a List<Dictionary<string, Statistic>>, not a simple Dictionary<string, Statistic>. Commented Apr 29, 2020 at 16:49

2 Answers 2

2

You can handle this tricky JSON using a simple JsonConverter for your Statistic class like this:

class StatisticConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Statistic);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        JProperty prop = jo.Properties().First();
        Statistic stat = new Statistic
        {
            Name = prop.Name,
            Min = (int)prop.Value["min"],
            Max = (int)prop.Value["max"]
        };
        return stat;
    }

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

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

To use it, add a [JsonConverter] attribute to the Statistic class and it should work as expected:

[JsonConverter(typeof(StatisticConverter))]
public class Statistic
{
    public string Name { get; set; }
    public int Min { get; set; }
    public int Max { get; set; }
}

Here is a working demo: https://dotnetfiddle.net/H7wR0g

If you cannot (or don't want to) add an attribute to the Statistic class, you can also use the converter by passing it to the DeserializeObject method like this:

var container = JsonConvert.DeserializeObject<Container>(json, new StatisticConverter());

Fiddle: https://dotnetfiddle.net/UTLzBk

Sign up to request clarification or add additional context in comments.

4 Comments

If the model is coming from a library, can he add the attribute needed for the converter? That's why I didn't got for a converter-based solution, but I may be wrong :)
@StepTNT You can also use the converter by passing it to JsonConvert.DeserializeObject() if you are not able to add an attribute to the Statistic class
@StepTNT I've updated my answer to show the alternative approach.
This makes your solution better than mine then, I didn't know you could pass converters like that!
1

Given the provided JSON, we can't directly go for a Dictionary structure, this is because we have an array of objects where each object has the Statistic name as property name (while it should be a value for a property named Name)

To avoid dealing with custom converters, here's a quick (non production-ready!) solution that can give you a good starting point. The idea is to deserialize it as a dynamic, then extract the statistics object as a JArray and build your result structure from there.

Please note that I'm not checking anything for null, so this works as long as the input is well-formed (you need Newtonsoft.Json from NuGet).

using System;
using System.Linq;
using Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace StackOverflow
{
    internal class Program
    {
        private static readonly string _inputJson =
            @"
{
    ""statistics"": [
        {
            ""Strength"": {
                ""min"": 16,
                ""max"": 20
            }
        },
        {
            ""Dexterity"": {
                ""min"": 16,
                ""max"": 20
            }
        }]
}
";

        private static void Main(string[] args)
        {
            var tempObject = JsonConvert.DeserializeObject<dynamic>(_inputJson).statistics as JArray;

            var result = new Container
            {
                Statistics = tempObject.Select(obj =>
                {
                    var token = obj.First as JProperty;
                    var stats = token.Value;
                    return new Statistic
                    {
                        Name = token.Name,
                        Min = Convert.ToInt32((stats["min"] as JValue).Value),
                        Max = Convert.ToInt32((stats["max"] as JValue).Value)
                    };
                }).ToArray()
            };

            foreach (var stat in result.Statistics) Console.WriteLine($"{stat.Name} = ({stat.Min}, {stat.Max})");
        }
    }
}

namespace Models
{
    public class Container
    {
        public Statistic[] Statistics { get; set; }
    }

    public class Statistic
    {
        //Contains the name of the statistic
        public string Name { get; set; }
        public int Min { get; set; }
        public int Max { get; set; }
    }
}

This is the output I'm getting:

Strength = (16, 20)

Dexterity = (16, 20)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.