2

I am having some trouble figuring out a clean (as possible) way to deserialize some JSON data in a particular format. I want to deserialize the data to strongly typed data object classes, pretty flexible regarding the specifics of this. Here is an example of what the data looks like:

{
    "timestamp": 1473730993,
    "total_players": 945,
    "max_score": 8961474,
    "players": {
            "Player1Username": [
            121,
            "somestring",
            679900,
            5,
            4497,
            "anotherString",
            "thirdString",
            "fourthString",
            123,
            22,
            "YetAnotherString"],
        "Player2Username": [
            886,
            "stillAstring",
            1677,
            1,
            9876,
            "alwaysAstring",
            "thirdString",
            "fourthString",
            876,
            77,
            "string"]
        }
}

The specific parts I am unsure about are:

  1. Would the collection of players be considered a dictionary? The username could serve as the key, but the value is throwing me off since it would be a mixed collection of string and integer values.
  2. A player is comprised entirely of unnamed values. I have pretty much always worked with JSON data that had named properties and values (ex. timestamp, total_players, etc. at the very top)

Say I have a top level class like this:

public class ScoreboardResults
{
    public int timestamp { get; set; }
    public int total_players { get; set; }
    public int max_score { get; set; }
    public List<Player> players { get; set; }
}

What would the Player object look like given that it is basically a key/value with the username serving as the key, and the value being a collection of mixed integers and strings? The data for each player element is always in the same order, so I know that the first value in the collection is their UniqueID, the second value is a player description, etc. I would like the player class to be something like this:

public class Player
{
    public string Username { get; set; }
    public int UniqueID { get; set; }
    public string PlayerDescription { get; set; }
    ....
    ....
    .... Following this pattern for all of the values in each player element
    ....
    ....
}

I am sure this is a pretty straightforward thing to do using JSON.NET, which is why I wanted to avoid any of the ideas I had on how to accomplish this. What I came up with would have been un-elegant and probably error prone to some degree during the serialization process.

EDIT

Here are the classes that get generated when using the past as JSON classes as suggested by snow_FFFFFF:

public class Rootobject
{
    public int timestamp { get; set; }
    public int total_players { get; set; }
    public int max_score { get; set; }
    public Players players { get; set; }
}

public class Players
{
    public object[] Player1Username { get; set; }
    public object[] Player2Username { get; set; }
}

What is not clear to me is how do I deserialize the JSON data in the "players" element as a List with Player1Username being a simple string property on the Player object. As for the collection of intermixed strings and integers, I am confident I can get those into individual properties on the Player object without issue.

dbc
  • 104,963
  • 20
  • 228
  • 340
Patrick
  • 148
  • 2
  • 7
  • 1
    You should be able to use a converter like the one in [JSON deserialization - Map array indices to properties with JSON.NET](https://stackoverflow.com/questions/36757412/json-deserialization-map-array-indices-to-properties-with-json-net) for your `Player` class, then make `players` be a `Dictionary`. – dbc Sep 13 '16 at 03:32
  • Here's a generic converter that does just what you want: [Deserializing JSON in Visual Basic .NET](https://stackoverflow.com/questions/31905958/deserializing-json-in-visual-basic-net). But, it's in VB.NET... – dbc Sep 13 '16 at 04:39
  • [@dbc](http://stackoverflow.com/users/3744182/dbc), I think that is what I am looking for. This looks like it should do exactly what I need. Thanks! – Patrick Sep 13 '16 at 04:47
  • I updated your title to more clearly reflect the problem. This question ends up being duplicated quite frequently but apparently isn't easy to find via search. – dbc Jul 23 '19 at 20:09

3 Answers3

6

The converter from Deserializing JSON in Visual Basic .NET should do what you need, suitably translated from VB.NET to c#:

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var objectType = value.GetType();
        var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException(string.Format("invalid type {0}.", objectType.FullName));
        writer.WriteStartArray();
        foreach (var property in SerializableProperties(contract))
        {
            var propertyValue = property.ValueProvider.GetValue(value);
            if (property.Converter != null && property.Converter.CanWrite)
                property.Converter.WriteJson(writer, propertyValue, serializer);
            else
                serializer.Serialize(writer, propertyValue);
        }
        writer.WriteEndArray();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException(string.Format("invalid type {0}.", objectType.FullName));

        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("token {0} was not JsonToken.StartArray", reader.TokenType));

        // Not implemented: JsonObjectContract.CreatorParameters, serialization callbacks, 
        existingValue = existingValue ?? contract.DefaultCreator();

        using (var enumerator = SerializableProperties(contract).GetEnumerator())
        {
            while (true)
            {
                switch (reader.ReadToContentAndAssert().TokenType)
                {
                    case JsonToken.EndArray:
                        return existingValue;

                    default:
                        if (!enumerator.MoveNext())
                        {
                            reader.Skip();
                            break;
                        }
                        var property = enumerator.Current;
                        object propertyValue;
                        // TODO:
                        // https://www.newtonsoft.com/json/help/html/Properties_T_Newtonsoft_Json_Serialization_JsonProperty.htm
                        // JsonProperty.ItemConverter, ItemIsReference, ItemReferenceLoopHandling, ItemTypeNameHandling, DefaultValue, DefaultValueHandling, ReferenceLoopHandling, Required, TypeNameHandling, ...
                        if (property.Converter != null && property.Converter.CanRead)
                            propertyValue = property.Converter.ReadJson(reader, property.PropertyType, property.ValueProvider.GetValue(existingValue), serializer);
                        else
                            propertyValue = serializer.Deserialize(reader, property.PropertyType);
                        property.ValueProvider.SetValue(existingValue, propertyValue);
                        break;
                }
            }
        }
    }

    static IEnumerable<JsonProperty> SerializableProperties(JsonObjectContract contract)
    {
        return contract.Properties.Where(p => !p.Ignored && p.Readable && p.Writable);
    }
}

public static partial class JsonExtensions
{
    public static JsonReader ReadToContentAndAssert(this JsonReader reader)
    {
        return reader.ReadAndAssert().MoveToContentAndAssert();
    }

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

Next, add the converter to your Player class, and indicate the order of each property using JsonPropertyAttribute.Order:

[JsonConverter(typeof(ObjectToArrayConverter<Player>))]
public class Player
{
    [JsonProperty(Order = 1)]
    public int UniqueID { get; set; }
    [JsonProperty(Order = 2)]
    public string PlayerDescription { get; set; }
    // Other fields as required.
}

Then finally, declare your root object as follows:

public class ScoreboardResults
{
    public int timestamp { get; set; }
    public int total_players { get; set; }
    public int max_score { get; set; }
    public Dictionary<string, Player> players { get; set; }
}

Note that I have moved Username out of the Player class and into the dictionary, as a key.

Note that data contract attributes can be used instead of Newtonsoft attributes to specify order:

[JsonConverter(typeof(ObjectToArrayConverter<Player>))]
[DataContract]
public class Player
{
    [DataMember(Order = 1)]
    public int UniqueID { get; set; }
    [DataMember(Order = 2)]
    public string PlayerDescription { get; set; }
    // Other fields as required.
}

Demo fiddles here, here and here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This worked perfectly. Couldn't have asked for a cleaner solution. The fact that it is generic and reusable is awesome. Thanks for a great solution! – Patrick Sep 13 '16 at 06:05
  • Unfortunately the converter is not recursive. – stricq Feb 15 '18 at 02:59
  • It doesn't work when properties have JsonConverterAttribute. How can I Serialize and Deserialize with JsonConverter? – Denis535 Jun 09 '19 at 17:15
  • @wishmaster35 - answer updated, however in the future you really should ask another question **with a new [mcve] that demonstrates your problem** since the existing answer meet the needs of this question. – dbc Jun 10 '19 at 22:14
5

A good way to get started would be to let visual studio generate your class based on the JSON. Open a blank class file and go to EDIT -> PASTE SPECIAL -> PASTE JSON As CLASSES.

This will generate a file with the necessary class(es) to serialize/deserialize your JSON.

snow_FFFFFF
  • 3,235
  • 17
  • 29
  • 1
    How I never knew about this feature is beyond me. I had always used json2csharp, which is similar but it is great to have that functionality directly in VS. When I do that for my example data in the OP, it generates a Players class with two properties of the type object[]. They are named Player1Username, and Player2Username. Is there a way to get the value "Player1Username" and set it to a string Username property on a Player object containing the array of the other values associated with each player? – Patrick Sep 13 '16 at 03:40
  • I think they are type object[] because you just have a bunch of random values as opposed to normal name/value pairs that you would expect in JSON. It sounds like you don't have control over the json, so I guess you can't change it. However, you essentially have an array of values of different types, so it seems like object[] is the best you can do. Maybe just add some other readonly properties for parsing the object[]. – snow_FFFFFF Sep 13 '16 at 03:54
  • Yep, and that is presenting the problem for me. I will make an edit to the OP so I can format the code and hopefully explain the issue more clearly. – Patrick Sep 13 '16 at 04:31
0

Try this

Create a class like below

Note : you can use Paste Special option in visual studio to generate all the classes related to the JSON

Edit -> Paste Special -> Paste Json As Classes

it will create all the classes related to the JSON

Note : refer this I have already answer similar like this..

Community
  • 1
  • 1
Shakir Ahamed
  • 1,290
  • 3
  • 16
  • 39
  • This has already been posted. It also does not solve the actual problem. It is generating a strongly typed property for each player (username) in the players collection. I am trying to make it use a single generic Player object with the username being a property within it. – Patrick Sep 13 '16 at 04:29
  • 1
    Also see this link: https://stackoverflow.com/questions/44405559/c-sharp-looping-array-object-multidimensional-from-ajax-post/44406004#44406004 – Jenish Zinzuvadiya Jun 07 '17 at 08:17