反序列化 [price, quantity] 数组
Deserializing array of [price, quantity]
这是 the endpoint 我正在尝试反序列化。问题在 bids
、asks
和 changes
中。我该如何处理? API 表示它是 array of [price, quantity]
.
片段
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public record Depth(
[property: JsonPropertyName("type")] DepthType Type,
[property: JsonPropertyName("pair")] string Pair,
[property: JsonPropertyName("sequence")] long Sequence,
[property: JsonPropertyName("asks")] List<List<string>> Asks,
//[property: JsonPropertyName("bids")] IEnumerable<Level> Bids,
[property: JsonPropertyName("prev_sequence")] long PreviousSequence
//[property: JsonPropertyName("changes")] IEnumerable<Changes> Changes
);
[JsonConverter(typeof(JsonStringEnumMemberConverter))]
public enum DepthType
{
[EnumMember(Value = "snapshot")]
Snapshot,
[EnumMember(Value = "update")]
Update
}
public record Level(decimal Price, decimal Quantity);
public record Changes(string Side, decimal Price, decimal Quantity);
回应
{
"channel":"depth",
"timestamp":1587929552250,
"module":"spot",
"data":{
"type":"snapshot",
"pair":"BTC-USDT",
"sequence":9,
"bids":[
[
"0.08000000",
"0.10000000"
]
],
"asks":[
[
"0.09000000",
"0.20000000"
]
]
}
}
{
"channel":"depth",
"timestamp":1587930311331,
"module":"spot",
"data":{
"type":"update",
"pair":"BTC-USDT",
"sequence":10,
"prev_sequence":9,
"changes":[
[
"sell",
"0.08500000",
"0.10000000"
]
]
}
}
尝试
[AttributeUsage(AttributeTargets.Property)]
public sealed class JsonPlainArrayIndexAttribute : Attribute
{
public JsonPlainArrayIndexAttribute(int index)
{
Index = index;
}
public int Index { get; }
}
public sealed class JsonPlainArrayConverter<T> : JsonConverter<T> where T : new()
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeof(T) == typeToConvert);
var props = typeToConvert.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanWrite && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop;
var arr = JsonSerializer.Deserialize<IEnumerable<JsonElement>>(ref reader, options);
if (arr is null)
{
return default;
}
var result = new T();
foreach (var (prop, value) in linq.Zip(arr))
{
prop.SetValue(result, value.Deserialize(prop.PropertyType, options));
}
return result;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var type = typeof(T);
var props = type.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanRead && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop.GetValue(value);
JsonSerializer.Serialize<IEnumerable<object>>(writer, linq, options);
}
}
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public record Depth(
[property: JsonPropertyName("type")] DepthType Type,
[property: JsonPropertyName("pair")] string Pair,
[property: JsonPropertyName("sequence")] long Sequence,
[property: JsonPropertyName("asks")] BookLevel Asks,
[property: JsonPropertyName("bids")] BookLevel Bids,
[property: JsonPropertyName("prev_sequence")] long PreviousSequence
//[property: JsonPropertyName("changes")] IEnumerable<Changes> Changes
);
[JsonConverter(typeof(JsonStringEnumMemberConverter))]
public enum DepthType
{
[EnumMember(Value = "snapshot")]
Snapshot,
[EnumMember(Value = "update")]
Update
}
[JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public record BookLevel
{
[JsonPlainArrayIndex(0)]
public string Price { get; init; }
[JsonPlainArrayIndex(1)]
public string Quantity { get; init; }
}
public record Changes(string Side, decimal Price, decimal Quantity);
来自 by yueyinqiu to 的转换器基本上可以 as-is 将您的 BookLevel
模型绑定到与其属性相对应的 JSON 值数组。但是,您有两个问题:
由于"bids"
和"asks"
在JSON中是锯齿状的二维数组,所以必须声明相应的属性为集合,例如:
public record Depth(
[property: JsonPropertyName("asks")] List<BookLevel> Asks,
[property: JsonPropertyName("bids")] List<BookLevel> Bids,
);
您已将 [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)
和 [JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))]
应用到 BookLevel
:
[JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public record BookLevel
{
[JsonPlainArrayIndex(0)]
public string Price { get; init; }
[JsonPlainArrayIndex(1)]
public string Quantity { get; init; }
}
事实证明,当你这样做时,System.Text.Json 将失败并出现一个神秘的内部 System.NullReferenceException
:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(JsonPropertyInfo jsonPropertyInfo)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.DetermineNumberHandlingForTypeInfo(Nullable`1 numberHandling)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.GetPolicies(Nullable`1 ignoreCondition, Nullable`1 declaringTypeNumberHandling)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.Initialize(Type parentClassType, Type declaredPropertyType, Type runtimePropertyType, ConverterStrategy runtimeClassType, MemberInfo memberInfo, Boolean isVirtual, JsonConverter converter, Nullable`1 ignoreCondition, Nullable`1 parentTypeNumberHandling, JsonSerializerOptions options)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo.CreateProperty(Type declaredPropertyType, Type runtimePropertyType, MemberInfo memberInfo, Type parentClassType, Boolean isVirtual, JsonConverter converter, JsonSerializerOptions options, Nullable`1 parentTypeNumberHandling, Nullable`1 ignoreCondition)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo..ctor(Type type, JsonConverter converter, Type runtimeType, JsonSerializerOptions options)
at System.Text.Json.JsonSerializerOptions.<InitializeForReflectionSerializer>g__CreateJsonTypeInfo|112_0(Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializerOptions.GetClassFromContextOrCreate(Type type)
at System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type type)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo.get_ElementTypeInfo()
at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryReadAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state, Object& value)
at System.Text.Json.Serialization.Converters.LargeObjectWithParameterizedConstructorConverter`1.ReadAndCacheConstructorArgument(ReadStack& state, Utf8JsonReader& reader, JsonParameterInfo jsonParameterInfo)
at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
这看起来是 System.Text.Json 本身的错误。如果你愿意,你可以举报。即使这两个属性不打算一起工作,System.Text.Json 也不应该抛出 NullReferenceException
.
演示 fiddle 此处:https://dotnetfiddle.net/sFWIJg.
作为问题 #2 的解决方法,您可以解封 JsonPlainArrayConverter<T>
以允许子类在反序列化特定属性时使用自定义选项,如下所示:
public sealed class JsonNumberHandlingPlainArrayConverter<T> : JsonPlainArrayConverter<T> where T : new()
{
protected override JsonSerializerOptions CustomizePropertyOptions(PropertyInfo info, JsonSerializerOptions options) =>
new JsonSerializerOptions(options)
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
}
// JsonPlainArrayIndexAttribute and JsonPlainArrayConverter<T> adapted from this answer
// By https://whosebug.com/users/15283686/yueyinqiu
// To
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class JsonPlainArrayIndexAttribute : Attribute
{
public int Index { get; }
public JsonPlainArrayIndexAttribute(int index) => this.Index = index;
}
public class JsonPlainArrayConverter<T> : JsonConverter<T> where T : new()
{
protected virtual JsonSerializerOptions CustomizePropertyOptions(PropertyInfo info, JsonSerializerOptions options) => options;
public override T? Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeof(T) == typeToConvert);
var props = typeToConvert.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanWrite && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop;
var arr = JsonSerializer.Deserialize<IEnumerable<JsonElement>>(ref reader, options);
if (arr is null)
return default;
var result = new T();
foreach (var (prop, value) in linq.Zip(arr))
{
prop.SetValue(result, value.Deserialize(prop.PropertyType, CustomizePropertyOptions(prop, options)));
}
return result;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var type = typeof(T);
var props = type.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanRead && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop.GetValue(value);
JsonSerializer.Serialize<IEnumerable<object>>(writer, linq, options);
}
}
然后修改BookLevel
如下:
[JsonConverter(typeof(JsonNumberHandlingPlainArrayConverter<BookLevel>))]
public record BookLevel
{
[JsonPlainArrayIndex(0)]
public decimal Price { get; init; }
[JsonPlainArrayIndex(1)]
public decimal Quantity { get; init; }
}
演示 fiddle 此处:https://dotnetfiddle.net/AwtFni
这是 the endpoint 我正在尝试反序列化。问题在 bids
、asks
和 changes
中。我该如何处理? API 表示它是 array of [price, quantity]
.
片段
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public record Depth(
[property: JsonPropertyName("type")] DepthType Type,
[property: JsonPropertyName("pair")] string Pair,
[property: JsonPropertyName("sequence")] long Sequence,
[property: JsonPropertyName("asks")] List<List<string>> Asks,
//[property: JsonPropertyName("bids")] IEnumerable<Level> Bids,
[property: JsonPropertyName("prev_sequence")] long PreviousSequence
//[property: JsonPropertyName("changes")] IEnumerable<Changes> Changes
);
[JsonConverter(typeof(JsonStringEnumMemberConverter))]
public enum DepthType
{
[EnumMember(Value = "snapshot")]
Snapshot,
[EnumMember(Value = "update")]
Update
}
public record Level(decimal Price, decimal Quantity);
public record Changes(string Side, decimal Price, decimal Quantity);
回应
{
"channel":"depth",
"timestamp":1587929552250,
"module":"spot",
"data":{
"type":"snapshot",
"pair":"BTC-USDT",
"sequence":9,
"bids":[
[
"0.08000000",
"0.10000000"
]
],
"asks":[
[
"0.09000000",
"0.20000000"
]
]
}
}
{
"channel":"depth",
"timestamp":1587930311331,
"module":"spot",
"data":{
"type":"update",
"pair":"BTC-USDT",
"sequence":10,
"prev_sequence":9,
"changes":[
[
"sell",
"0.08500000",
"0.10000000"
]
]
}
}
尝试
[AttributeUsage(AttributeTargets.Property)]
public sealed class JsonPlainArrayIndexAttribute : Attribute
{
public JsonPlainArrayIndexAttribute(int index)
{
Index = index;
}
public int Index { get; }
}
public sealed class JsonPlainArrayConverter<T> : JsonConverter<T> where T : new()
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeof(T) == typeToConvert);
var props = typeToConvert.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanWrite && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop;
var arr = JsonSerializer.Deserialize<IEnumerable<JsonElement>>(ref reader, options);
if (arr is null)
{
return default;
}
var result = new T();
foreach (var (prop, value) in linq.Zip(arr))
{
prop.SetValue(result, value.Deserialize(prop.PropertyType, options));
}
return result;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var type = typeof(T);
var props = type.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanRead && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop.GetValue(value);
JsonSerializer.Serialize<IEnumerable<object>>(writer, linq, options);
}
}
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public record Depth(
[property: JsonPropertyName("type")] DepthType Type,
[property: JsonPropertyName("pair")] string Pair,
[property: JsonPropertyName("sequence")] long Sequence,
[property: JsonPropertyName("asks")] BookLevel Asks,
[property: JsonPropertyName("bids")] BookLevel Bids,
[property: JsonPropertyName("prev_sequence")] long PreviousSequence
//[property: JsonPropertyName("changes")] IEnumerable<Changes> Changes
);
[JsonConverter(typeof(JsonStringEnumMemberConverter))]
public enum DepthType
{
[EnumMember(Value = "snapshot")]
Snapshot,
[EnumMember(Value = "update")]
Update
}
[JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public record BookLevel
{
[JsonPlainArrayIndex(0)]
public string Price { get; init; }
[JsonPlainArrayIndex(1)]
public string Quantity { get; init; }
}
public record Changes(string Side, decimal Price, decimal Quantity);
来自 BookLevel
模型绑定到与其属性相对应的 JSON 值数组。但是,您有两个问题:
由于
"bids"
和"asks"
在JSON中是锯齿状的二维数组,所以必须声明相应的属性为集合,例如:public record Depth( [property: JsonPropertyName("asks")] List<BookLevel> Asks, [property: JsonPropertyName("bids")] List<BookLevel> Bids, );
您已将
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)
和[JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))]
应用到BookLevel
:[JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public record BookLevel { [JsonPlainArrayIndex(0)] public string Price { get; init; } [JsonPlainArrayIndex(1)] public string Quantity { get; init; } }
事实证明,当你这样做时,System.Text.Json 将失败并出现一个神秘的内部
System.NullReferenceException
:System.NullReferenceException: Object reference not set to an instance of an object. at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(JsonPropertyInfo jsonPropertyInfo) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.DetermineNumberHandlingForTypeInfo(Nullable`1 numberHandling) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.GetPolicies(Nullable`1 ignoreCondition, Nullable`1 declaringTypeNumberHandling) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.Initialize(Type parentClassType, Type declaredPropertyType, Type runtimePropertyType, ConverterStrategy runtimeClassType, MemberInfo memberInfo, Boolean isVirtual, JsonConverter converter, Nullable`1 ignoreCondition, Nullable`1 parentTypeNumberHandling, JsonSerializerOptions options) at System.Text.Json.Serialization.Metadata.JsonTypeInfo.CreateProperty(Type declaredPropertyType, Type runtimePropertyType, MemberInfo memberInfo, Type parentClassType, Boolean isVirtual, JsonConverter converter, JsonSerializerOptions options, Nullable`1 parentTypeNumberHandling, Nullable`1 ignoreCondition) at System.Text.Json.Serialization.Metadata.JsonTypeInfo..ctor(Type type, JsonConverter converter, Type runtimeType, JsonSerializerOptions options) at System.Text.Json.JsonSerializerOptions.<InitializeForReflectionSerializer>g__CreateJsonTypeInfo|112_0(Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializerOptions.GetClassFromContextOrCreate(Type type) at System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type type) at System.Text.Json.Serialization.Metadata.JsonTypeInfo.get_ElementTypeInfo() at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value) at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter`1.TryReadAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state, Object& value) at System.Text.Json.Serialization.Converters.LargeObjectWithParameterizedConstructorConverter`1.ReadAndCacheConstructorArgument(ReadStack& state, Utf8JsonReader& reader, JsonParameterInfo jsonParameterInfo) at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount) at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo) at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
这看起来是 System.Text.Json 本身的错误。如果你愿意,你可以举报。即使这两个属性不打算一起工作,System.Text.Json 也不应该抛出
NullReferenceException
.演示 fiddle 此处:https://dotnetfiddle.net/sFWIJg.
作为问题 #2 的解决方法,您可以解封 JsonPlainArrayConverter<T>
以允许子类在反序列化特定属性时使用自定义选项,如下所示:
public sealed class JsonNumberHandlingPlainArrayConverter<T> : JsonPlainArrayConverter<T> where T : new()
{
protected override JsonSerializerOptions CustomizePropertyOptions(PropertyInfo info, JsonSerializerOptions options) =>
new JsonSerializerOptions(options)
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
}
// JsonPlainArrayIndexAttribute and JsonPlainArrayConverter<T> adapted from this answer
// By https://whosebug.com/users/15283686/yueyinqiu
// To
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class JsonPlainArrayIndexAttribute : Attribute
{
public int Index { get; }
public JsonPlainArrayIndexAttribute(int index) => this.Index = index;
}
public class JsonPlainArrayConverter<T> : JsonConverter<T> where T : new()
{
protected virtual JsonSerializerOptions CustomizePropertyOptions(PropertyInfo info, JsonSerializerOptions options) => options;
public override T? Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeof(T) == typeToConvert);
var props = typeToConvert.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanWrite && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop;
var arr = JsonSerializer.Deserialize<IEnumerable<JsonElement>>(ref reader, options);
if (arr is null)
return default;
var result = new T();
foreach (var (prop, value) in linq.Zip(arr))
{
prop.SetValue(result, value.Deserialize(prop.PropertyType, CustomizePropertyOptions(prop, options)));
}
return result;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var type = typeof(T);
var props = type.GetProperties();
var linq = from prop in props
let attr = prop.GetCustomAttributes(typeof(JsonPlainArrayIndexAttribute), true)
where prop.CanRead && attr.Length is 1
orderby ((JsonPlainArrayIndexAttribute)attr[0]).Index
select prop.GetValue(value);
JsonSerializer.Serialize<IEnumerable<object>>(writer, linq, options);
}
}
然后修改BookLevel
如下:
[JsonConverter(typeof(JsonNumberHandlingPlainArrayConverter<BookLevel>))]
public record BookLevel
{
[JsonPlainArrayIndex(0)]
public decimal Price { get; init; }
[JsonPlainArrayIndex(1)]
public decimal Quantity { get; init; }
}
演示 fiddle 此处:https://dotnetfiddle.net/AwtFni