c# - 将带有键的嵌套 JObject 反序列化为 List
问题描述
我需要使用 Newtonsoft.Json 将 json 反序列化回对象实例。
但是,它是一个列表类型的对象,条目的键对我很有用。
我不知道如何在不手动映射字段的情况下自动反序列化。
这是回应:
{
coins: {
365Coin: {
id: 74,
tag: "365",
algorithm: "Keccak",
lagging: true,
listed: false,
status: "No available stats",
testing: false
},
Aiden: {
id: 65,
tag: "ADN",
algorithm: "Scrypt-OG",
lagging: true,
listed: false,
status: "No available stats",
testing: false
},
Adzcoin: {
id: 157,
tag: "ADZ",
algorithm: "X11",
lagging: false,
listed: false,
status: "Active",
testing: false
}
... [With various key representing the name of coins]
}
}
完整回复:https ://whattomine.com/calculators.json
我对这门课的最佳猜测是:
internal class WhatToMineCalculatorsResponse
{
// Should be Dictionary???
[JsonProperty("coins")]
public IList<WhatToMineCalculatorResponse> Coins { get; set; }
}
internal class WhatToMineCalculatorResponse
{
// I want the key set in this field
public string Name { get; set; }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tag")]
public string Symbol { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("algorithm")]
public string Algo { get; set; }
[JsonProperty("listed")]
public bool IsListed { get; set; }
}
请注意,我希望将密钥包含在我的班级中,而不是作为字典的密钥。以后很难找回密钥。
解决方案
您不能完全通过属性指定IList<T>
for someT
应序列化为 JSON 对象。正如其序列化指南中所解释的,Newtonsoft 将字典和哈希表映射到 JSON 对象,但将所有其他枚举、列表和数组映射到 JSON 数组。相反,您将不得不使用自定义JsonConverter
.
首先,定义以下转换器:
internal class WhatToMineCalculatorResponseListConverter : KeyedListToJsonObjectConverterBase<WhatToMineCalculatorResponse>
{
protected override string KeyPropertyUnderlyingName => nameof(WhatToMineCalculatorResponse.Name);
}
public abstract class KeyedListToJsonObjectConverterBase<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType.IsArray)
return false;
return typeof(IList<T>).IsAssignableFrom(objectType);
}
protected abstract string KeyPropertyUnderlyingName { get; }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Get the key property name from the underlying name
var itemContract = serializer.ContractResolver.ResolveContract(typeof(T)) as JsonObjectContract;
if (itemContract == null)
throw new JsonSerializationException(string.Format("type {0} is not serialized as a JSON object"));
var keyProperty = itemContract.Properties.Where(p => p.UnderlyingName == KeyPropertyUnderlyingName).SingleOrDefault();
if (keyProperty == null)
throw new JsonSerializationException(string.Format("Key property {0} not found", KeyPropertyUnderlyingName));
// Validate initial token.
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0} at {1}", reader.TokenType, reader.Path));
// Allocate the List<T>. (It might be some subclass of List<T>, so use the default creator.
var list = existingValue as ICollection<T> ?? (ICollection<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
// Process each key/value pair.
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.EndObject:
return list;
case JsonToken.PropertyName:
{
// Get the name.
var name = (string)reader.Value;
reader.ReadAndAssert();
// Load the object
var jItem = JObject.Load(reader);
// Add the name property
jItem.Add(keyProperty.PropertyName, name);
// Deserialize the item and add it to the list.
list.Add(jItem.ToObject<T>(serializer));
}
break;
default:
{
throw new JsonSerializationException(string.Format("Unexpected token {0} at {1}", reader.TokenType, reader.Path));
}
}
}
// Should not come here.
throw new JsonSerializationException("Unclosed object at path: " + reader.Path);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Get the key property name from the underlying name
var itemContract = serializer.ContractResolver.ResolveContract(typeof(T)) as JsonObjectContract;
if (itemContract == null)
throw new JsonSerializationException(string.Format("type {0} is not serialized as a JSON object"));
var keyProperty = itemContract.Properties.Where(p => p.UnderlyingName == KeyPropertyUnderlyingName).SingleOrDefault();
if (keyProperty == null)
throw new JsonSerializationException(string.Format("Key property {0} not found", KeyPropertyUnderlyingName));
var converters = serializer.Converters.ToArray();
var list = (IEnumerable<T>)value;
writer.WriteStartObject();
foreach (var item in list)
{
var jItem = JObject.FromObject(item, serializer);
var name = (string)jItem[keyProperty.PropertyName];
jItem.Remove(keyProperty.PropertyName);
writer.WritePropertyName(name);
jItem.WriteTo(writer, converters);
}
writer.WriteEndObject();
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
{
new JsonReaderException(string.Format("Unexpected end at path {0}", reader.Path));
}
}
}
然后,您可以按如下方式反序列化:
var settings = new JsonSerializerSettings
{
Converters = { new WhatToMineCalculatorResponseListConverter() },
};
var root = JsonConvert.DeserializeObject<WhatToMineCalculatorsResponse>(responseString, settings);
笔记:
在序列化 a并且类型具有要用作 JSON 对象属性名称的特定属性
KeyedListToJsonObjectConverterBase<T>
的任何情况下,都可以重用基类转换器。只需覆盖并返回实际的 .Net 属性名称(不是序列化名称)。List<T>
T
KeyPropertyUnderlyingName
代码看起来有点复杂,因为我做了
KeyedListToJsonObjectConverterBase<T>
足够通用的处理来处理 key 属性为只读的情况,例如:internal class WhatToMineCalculatorResponse { readonly string _name; public WhatToMineCalculatorResponse(string name) { this._name = name; } // I want the key set in this field public string Name { get { return _name; } } // Remainder of class unchanged }
在这里工作.Net 小提琴。
推荐阅读
- java - 如何测试文本视图是否包含特定链接?
- python - 条件在python中工作异常:
- git - 通过 Github Actions CI/CD 访问服务器时 Git Permission denied (publickey)
- python-3.x - 如何在不同的shell中打开两个py文件
- c# - 是否可以在 C# 中执行“递归参数多态性”
- excel - Excel VBA:如何将范围转换为数字并将列中的输出作为列表
- amazon-redshift - 将子查询连接到另一个红移
- mysql - MYSQL EAV 连接查询优化
- javascript - 在 iframe 中拖放在 Spring 中不起作用
- javascript - LinkError:WebAssembly.instantiate() 函数导入需要可调用