如何将具有固定模式的值数组反序列化为强类型数据类?
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<string, Player>
。
这是一个通用转换器,可以满足您的需求: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/…以上是关于如何将具有固定模式的值数组反序列化为强类型数据类?的主要内容,如果未能解决你的问题,请参考以下文章
无法将 appsettings IConfiguration 部分反序列化为类型化元素的数组
无法将当前 JSON 数组(例如 [1,2,3])反序列化为具有复杂和嵌套对象的类型