¿Cómo deserializar una matriz de valores con un esquema fijo en una clase de datos fuertemente tipada?

Resuelto Patrick asked hace 8 años • 3 respuestas

Tengo problemas para encontrar una manera limpia (lo más limpia posible) de deserializar algunos datos JSON en un formato particular. Quiero deserializar los datos en clases de objetos de datos fuertemente tipados, bastante flexibles con respecto a los detalles de esto. A continuación se muestra un ejemplo de cómo se ven los datos:

{
    "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"]
        }
}

Las partes específicas de las que no estoy seguro son:

  1. ¿Se consideraría la colección de jugadores un diccionario? El nombre de usuario podría servir como clave, pero el valor me desconcierta ya que sería una colección mixta de valores de cadena y enteros.
  2. Un jugador se compone enteramente de valores sin nombre. Casi siempre he trabajado con datos JSON que tenían propiedades y valores nombrados (por ejemplo, marca de tiempo, total_players, etc. en la parte superior)

Digamos que tengo una clase de alto nivel como esta:

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; }
}

¿Cómo se vería el objeto Player dado que es básicamente una clave/valor con el nombre de usuario como clave y el valor como una colección de enteros y cadenas mixtos? Los datos de cada elemento del jugador siempre están en el mismo orden, por lo que sé que el primer valor de la colección es su ID único, el segundo valor es la descripción del jugador, etc. Me gustaría que la clase del jugador fuera algo como esto:

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
    ....
    ....
}

Estoy seguro de que esto es bastante sencillo de hacer usando JSON.NET, por lo que quería evitar cualquiera de las ideas que tenía sobre cómo lograrlo. Lo que se me ocurrió habría sido poco elegante y probablemente propenso a errores hasta cierto punto durante el proceso de serialización.

EDITAR

Estas son las clases que se generan cuando se usa el pasado como clases JSON como lo sugiere 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; }
}

Lo que no me queda claro es cómo deserializar los datos JSON en el elemento "jugadores" como una Lista con Player1Username como una propiedad de cadena simple en el objeto Player. En cuanto a la colección de cadenas y números enteros entremezclados, estoy seguro de que puedo incluirlos en propiedades individuales en el objeto Player sin problemas.

Patrick avatar Sep 13 '16 09:09 Patrick
Aceptado

El convertidor de Deserializing JSON en Visual Basic .NET debería hacer lo que necesita, adecuadamente traducido de VB.NET a 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;
    }
}

A continuación, agregue el convertidor a su Playerclase e indique el orden de cada propiedad usando 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.
}

Finalmente, declara tu objeto raíz de la siguiente manera:

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; }
}

Tenga en cuenta que salí Usernamede la Playerclase y entré en el diccionario, como clave.

Tenga en cuenta que se pueden utilizar atributos de contrato de datos en lugar de atributos de Newtonsoft para especificar el orden:

[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.
}

Violines de demostración aquí , aquí y aquí .

dbc avatar Sep 13 '2016 04:09 dbc

Una buena manera de comenzar sería dejar que Visual Studio genere su clase basada en JSON. Abra un archivo de clase en blanco y vaya a EDITAR -> PEGAR ESPECIAL -> PEGAR JSON como CLASES.

Esto generará un archivo con las clases necesarias para serializar/deserializar su JSON.

snow_FFFFFF avatar Sep 13 '2016 03:09 snow_FFFFFF