如何将具有固定模式的值数组反序列化为强类型数据类?

Posted

技术标签:

【中文标题】如何将具有固定模式的值数组反序列化为强类型数据类?【英文标题】:How to deserialize an array of values with a fixed schema to a strongly typed data class? 【发布时间】:2017-01-20 13:15:50 【问题描述】:

我在找出一种干净(尽可能)的方法来反序列化某些特定格式的 JSON 数据时遇到了一些麻烦。我想将数据反序列化为强类型数据对象类,这在细节方面非常灵活。以下是数据的示例:


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

我不确定的具体部分是:

    玩家的集合会被视为字典吗?用户名可以作为键,但值让我很反感,因为它是字符串和整数值的混合集合。 玩家完全由未命名的值组成。我几乎总是使用具有命名属性和值(例如时间戳、total_players 等位于最顶部)的 JSON 数据

假设我有一个像这样的***课程:

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; 

如果 Player 对象基本上是一个以用户名作为键的键/值,并且值是混合整数和字符串的集合,它会是什么样子?每个玩家元素的数据总是以相同的顺序排列,所以我知道集合中的第一个值是他们的 UniqueID,第二个值是玩家描述等。我希望玩家类是这样的:

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

我确信这是使用 JSON.NET 做的一件非常简单的事情,这就是为什么我想避免我对如何实现这一点的任何想法。在序列化过程中,我想出的内容可能并不优雅,并且可能在某种程度上容易出错。

编辑

以下是使用过去作为 JSON 类时生成的类,如 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; 

我不清楚的是如何将“players”元素中的 JSON 数据反序列化为一个 List,其中 Player1Username 是 Player 对象上的一个简单字符串属性。至于混合字符串和整数的集合,我相信我可以毫无问题地将它们放入 Player 对象的各个属性中。

【问题讨论】:

您应该能够为您的Player 类使用类似JSON deserialization - Map array indices to properties with JSON.NET 中的转换器,然后将players 设为Dictionary&lt;string, Player&gt; 这是一个通用转换器,可以满足您的需求:Deserializing JSON in Visual Basic .NET。但是,它在 VB.NET 中...... @dbc,我想这就是我要找的。这看起来应该完全符合我的需要。谢谢! 我更新了您的标题以更清楚地反映问题。这个问题最终经常被重复,但显然不容易通过搜索找到。 【参考方案1】:

Deserializing JSON in Visual Basic .NET 的转换器应该可以满足您的需求,从 VB.NET 适当地转换为 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;
    

接下来,将转换器添加到您的Player 类中,并使用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.

最后,如下声明你的根对象:

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; 

请注意,我已将 Username 移出 Player 类并作为键移到字典中。

注意data contract attributes可以用来代替Newtonsoft属性来指定顺序:

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

演示小提琴here、here 和here。

【讨论】:

这非常有效。不能要求更清洁的解决方案。它是通用的和可重用的,这一点很棒。感谢您提供出色的解决方案! 不幸的是转换器不是递归的。 属性有 JsonConverterAttribute 时不起作用。如何使用 JsonConverter 进行序列化和反序列化? @wishmaster35 - 答案已更新,但是将来您真的应该问另一个问题用一个新的minimal reproducible example 来证明您的问题,因为现有答案可以满足此需求问题。【参考方案2】:

一个好的入门方法是让 Visual Studio 基于 JSON 生成您的类。打开一个空白类文件,然后转到 EDIT -> PASTE SPECIAL -> PASTE JSON As CLASSES。

这将生成一个包含必要类的文件来序列化/反序列化您的 JSON。

【讨论】:

我从不知道这个功能是怎么回事。我一直使用 json2csharp,它很相似,但直接在 VS 中拥有该功能很棒。当我对 OP 中的示例数据执行此操作时,它会生成一个具有两个 object[] 类型属性的 Players 类。它们被命名为 Player1Username 和 Player2Username。有没有办法获取值“Player1Username”并将其设置为包含与每个玩家关联的其他值数组的 Player 对象上的字符串 Username 属性? 我认为它们是 object[] 类型,因为您只有一堆随机值,而不是您在 JSON 中期望的普通名称/值对。听起来您无法控制 json,所以我想您无法更改它。但是,您实际上拥有一组不同类型的值,因此 object[] 似乎是您能做的最好的事情。也许只是添加一些其他只读属性来解析对象[]。 是的,这对我来说是个问题。我将对 OP 进行编辑,以便我可以格式化代码并希望更清楚地解释问题。【参考方案3】:

试试这个

创建一个如下所示的类

注意:您可以使用 Visual Studio 中的“选择性粘贴”选项来生成与 JSON 相关的所有类

编辑 -> 选择性粘贴 -> 将 Json 粘贴为类

它将创建所有与 JSON 相关的类

注意:参考this我已经回答过类似的问题..

【讨论】:

这已经发布了。它也不能解决实际问题。它为玩家集合中的每个玩家(用户名)生成一个强类型属性。我试图让它使用单个通用 Player 对象,其中用户名是其中的一个属性。 另见此链接:***.com/questions/44405559/…

以上是关于如何将具有固定模式的值数组反序列化为强类型数据类?的主要内容,如果未能解决你的问题,请参考以下文章

Json.NET:将嵌套数组反序列化为强类型对象

无法将 appsettings IConfiguration 部分反序列化为类型化元素的数组

无法将当前 JSON 数组(例如 [1,2,3])反序列化为具有复杂和嵌套对象的类型

将 JSON 反序列化为具有通用字段的 Serializable 类 - 错误:不允许类型参数中的星形投影

无法将 JSON 数组反序列化为类型 - Json.NET

如何使用 kotlinx 序列化将值数组反序列化为集合