System.Text.Json 无法反序列化 List<List<object>>
System.Text.Json fails to deserialize List<List<object>>
我正在使用 System.Text.Json
,但无法反序列化 BookLevel[]
。 BookLevel 类似于 List<List<object>>
.
The JSON value could not be converted to Deribit.Models.BookLevel. Path: $.params.data.bids[0] | LineNumber: 0 | BytePositionInLine: 234.. Exception: JsonException
public record BookResponse
{
[JsonPropertyName("type")]
public string Type { get; init; } = null!;
[JsonPropertyName("timestamp")]
public long Timestamp { get; init; }
[JsonPropertyName("prev_change_id")]
public decimal PreviousChangeId { get; init; }
[JsonPropertyName("instrument_name")]
public string InstrumentName { get; init; } = null!;
[JsonPropertyName("change_id")]
public decimal ChangeId { get; init; }
[JsonPropertyName("bids")]
public BookLevel[] Bids { get; init; } = null!;
[JsonPropertyName("asks")]
public BookLevel[] Asks { get; init; } = null!;
}
public record BookLevel
{
[JsonPropertyOrder(1)]
public string Action { get; init; } = null!;
[JsonPropertyOrder(2)]
public decimal Amount { get; init; }
[JsonPropertyOrder(3)]
public decimal Price { get; init; }
}
{"jsonrpc":"2.0","method":"subscription","params":{"channel":"book.BTC-PERPETUAL.100ms","data":{"type":"change","timestamp":1648477437698,"prev_change_id":42599922395,"instrument_name":"BTC-PERPETUAL","change_id":42599922580,"bids":[["change",47452.0,55700.0],["change",47451.5,24170.0],["delete",47449.0,0.0],["new",47446.5,2130.0],["change",47440.5,56210.0],["new",47439.0,46520.0],["new",47438.0,660.0],["new",47437.0,47430.0],["change",47429.5,20000.0],["change",47429.0,2810.0],["change",47428.5,36460.0],["change",47428.0,3070.0],["new",47427.0,21110.0],["delete",47423.5,0.0],["new",47421.0,33400.0],["change",47420.5,33190.0],["new",47420.0,140.0],["change",47390.0,63980.0],["new",47382.0,85480.0],["delete",47381.0,0.0],["new",47379.5,32770.0]],"asks":[["change",47452.5,15950.0],["new",47467.0,101970.0],["delete",47467.5,0.0],["change",47469.0,1200.0],["change",47470.5,31470.0],["change",47471.5,2010.0],["change",47474.0,79380.0],["change",47474.5,47470.0],["new",47475.5,2970.0],["new",47476.0,21010.0],["change",47476.5,7630.0],["change",47477.0,42510.0],["change",47478.5,100.0],["delete",47480.0,0.0],["change",47482.5,5650.0],["delete",47485.5,0.0],["new",47494.0,150.0],["change",47494.5,43340.0],["new",47523.5,32590.0],["delete",47527.5,0.0]]}}}
返回 Newtonsoft.Json
我可以将 BookLevel
标记为 [JsonConverter(typeof(ObjectToArrayConverter<LevelEvent>))]
并使用以下内容。我如何用 System.Text.Json 做到这一点?
/// <summary>
/// Adapted from
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObjectToArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
static bool ShouldSkip(JsonProperty property)
{
return property.Ignored || !property.Readable || !property.Writable;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = value.GetType();
if (!(serializer.ContractResolver.ResolveContract(type) is JsonObjectContract contract))
{
throw new JsonSerializationException("invalid type " + type.FullName);
}
var list = contract.Properties.Where(p => !ShouldSkip(p)).Select(p => p.ValueProvider.GetValue(value));
serializer.Serialize(writer, list);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var token = JArray.Load(reader);
if (!(serializer.ContractResolver.ResolveContract(objectType) is JsonObjectContract contract))
{
throw new JsonSerializationException("invalid type " + objectType.FullName);
}
var value = existingValue ?? contract.DefaultCreator();
foreach (var pair in contract.Properties.Where(p => !ShouldSkip(p)).Zip(token, (p, v) => new { Value = v, Property = p }))
{
var propertyValue = pair.Value.ToObject(pair.Property.PropertyType, serializer);
pair.Property.ValueProvider.SetValue(value, propertyValue);
}
return value;
}
}
对于您的 BookLevel
,您可以:
using System.Text.Json;
using System.Text.Json.Serialization;
[JsonConverter(typeof(BookLevelConverter))]
public record BookLevel
{
public string Action { get; init; } = null!;
public decimal Amount { get; init; }
public decimal Price { get; init; }
}
public class BookLevelConverter : JsonConverter<BookLevel>
{
public override BookLevel? Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var arr = JsonSerializer.Deserialize<JsonElement[]>(ref reader, options);
return arr is null ? null : new BookLevel()
{
Action = arr[0].GetString()!,
Amount = arr[1].GetInt32(),
Price = arr[2].GetInt32()
};
}
public override void Write(
Utf8JsonWriter writer, BookLevel value, JsonSerializerOptions options)
{
var arr = new object[] { value.Action, value.Amount, value.Price };
JsonSerializer.Serialize(writer, arr, options);
}
}
但是你为什么要这样做?这是来自第三方服务的响应?
好吧,还有更通用的方法。尽管我已经在您的 BookLevel
上对其进行了测试,但我不能保证它适用于所有类型和所有边缘情况。也不知道有没有更简单的解决办法
所有具有 JsonPlainArrayIndexAttribute
的 public 属性将被序列化。 JsonIgnore
等属性不会生效。您可以通过不添加 JsonPlainArrayIndexAttribute
来忽略 属性。并且为了确保它可以成功反序列化,JsonSerializerOptions.DefaultIgnoreCondition
等选项也将被忽略。
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class JsonPlainArrayIndexAttribute : Attribute
{
readonly int index;
public JsonPlainArrayIndexAttribute(int index)
{
this.index = index;
}
public int Index
{
get { return index; }
}
}
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);
}
}
和 BookLevel
:
[JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))]
public record BookLevel
{
[JsonPlainArrayIndex(0)]
public string Action { get; init; } = null!;
[JsonPlainArrayIndex(1)]
public decimal Amount { get; init; }
[JsonPlainArrayIndex(2)]
public decimal Price { get; init; }
}
我正在使用 System.Text.Json
,但无法反序列化 BookLevel[]
。 BookLevel 类似于 List<List<object>>
.
The JSON value could not be converted to Deribit.Models.BookLevel. Path: $.params.data.bids[0] | LineNumber: 0 | BytePositionInLine: 234.. Exception: JsonException
public record BookResponse
{
[JsonPropertyName("type")]
public string Type { get; init; } = null!;
[JsonPropertyName("timestamp")]
public long Timestamp { get; init; }
[JsonPropertyName("prev_change_id")]
public decimal PreviousChangeId { get; init; }
[JsonPropertyName("instrument_name")]
public string InstrumentName { get; init; } = null!;
[JsonPropertyName("change_id")]
public decimal ChangeId { get; init; }
[JsonPropertyName("bids")]
public BookLevel[] Bids { get; init; } = null!;
[JsonPropertyName("asks")]
public BookLevel[] Asks { get; init; } = null!;
}
public record BookLevel
{
[JsonPropertyOrder(1)]
public string Action { get; init; } = null!;
[JsonPropertyOrder(2)]
public decimal Amount { get; init; }
[JsonPropertyOrder(3)]
public decimal Price { get; init; }
}
{"jsonrpc":"2.0","method":"subscription","params":{"channel":"book.BTC-PERPETUAL.100ms","data":{"type":"change","timestamp":1648477437698,"prev_change_id":42599922395,"instrument_name":"BTC-PERPETUAL","change_id":42599922580,"bids":[["change",47452.0,55700.0],["change",47451.5,24170.0],["delete",47449.0,0.0],["new",47446.5,2130.0],["change",47440.5,56210.0],["new",47439.0,46520.0],["new",47438.0,660.0],["new",47437.0,47430.0],["change",47429.5,20000.0],["change",47429.0,2810.0],["change",47428.5,36460.0],["change",47428.0,3070.0],["new",47427.0,21110.0],["delete",47423.5,0.0],["new",47421.0,33400.0],["change",47420.5,33190.0],["new",47420.0,140.0],["change",47390.0,63980.0],["new",47382.0,85480.0],["delete",47381.0,0.0],["new",47379.5,32770.0]],"asks":[["change",47452.5,15950.0],["new",47467.0,101970.0],["delete",47467.5,0.0],["change",47469.0,1200.0],["change",47470.5,31470.0],["change",47471.5,2010.0],["change",47474.0,79380.0],["change",47474.5,47470.0],["new",47475.5,2970.0],["new",47476.0,21010.0],["change",47476.5,7630.0],["change",47477.0,42510.0],["change",47478.5,100.0],["delete",47480.0,0.0],["change",47482.5,5650.0],["delete",47485.5,0.0],["new",47494.0,150.0],["change",47494.5,43340.0],["new",47523.5,32590.0],["delete",47527.5,0.0]]}}}
返回 Newtonsoft.Json
我可以将 BookLevel
标记为 [JsonConverter(typeof(ObjectToArrayConverter<LevelEvent>))]
并使用以下内容。我如何用 System.Text.Json 做到这一点?
/// <summary>
/// Adapted from
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObjectToArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
static bool ShouldSkip(JsonProperty property)
{
return property.Ignored || !property.Readable || !property.Writable;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = value.GetType();
if (!(serializer.ContractResolver.ResolveContract(type) is JsonObjectContract contract))
{
throw new JsonSerializationException("invalid type " + type.FullName);
}
var list = contract.Properties.Where(p => !ShouldSkip(p)).Select(p => p.ValueProvider.GetValue(value));
serializer.Serialize(writer, list);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var token = JArray.Load(reader);
if (!(serializer.ContractResolver.ResolveContract(objectType) is JsonObjectContract contract))
{
throw new JsonSerializationException("invalid type " + objectType.FullName);
}
var value = existingValue ?? contract.DefaultCreator();
foreach (var pair in contract.Properties.Where(p => !ShouldSkip(p)).Zip(token, (p, v) => new { Value = v, Property = p }))
{
var propertyValue = pair.Value.ToObject(pair.Property.PropertyType, serializer);
pair.Property.ValueProvider.SetValue(value, propertyValue);
}
return value;
}
}
对于您的 BookLevel
,您可以:
using System.Text.Json;
using System.Text.Json.Serialization;
[JsonConverter(typeof(BookLevelConverter))]
public record BookLevel
{
public string Action { get; init; } = null!;
public decimal Amount { get; init; }
public decimal Price { get; init; }
}
public class BookLevelConverter : JsonConverter<BookLevel>
{
public override BookLevel? Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var arr = JsonSerializer.Deserialize<JsonElement[]>(ref reader, options);
return arr is null ? null : new BookLevel()
{
Action = arr[0].GetString()!,
Amount = arr[1].GetInt32(),
Price = arr[2].GetInt32()
};
}
public override void Write(
Utf8JsonWriter writer, BookLevel value, JsonSerializerOptions options)
{
var arr = new object[] { value.Action, value.Amount, value.Price };
JsonSerializer.Serialize(writer, arr, options);
}
}
但是你为什么要这样做?这是来自第三方服务的响应?
好吧,还有更通用的方法。尽管我已经在您的 BookLevel
上对其进行了测试,但我不能保证它适用于所有类型和所有边缘情况。也不知道有没有更简单的解决办法
所有具有 JsonPlainArrayIndexAttribute
的 public 属性将被序列化。 JsonIgnore
等属性不会生效。您可以通过不添加 JsonPlainArrayIndexAttribute
来忽略 属性。并且为了确保它可以成功反序列化,JsonSerializerOptions.DefaultIgnoreCondition
等选项也将被忽略。
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class JsonPlainArrayIndexAttribute : Attribute
{
readonly int index;
public JsonPlainArrayIndexAttribute(int index)
{
this.index = index;
}
public int Index
{
get { return index; }
}
}
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);
}
}
和 BookLevel
:
[JsonConverter(typeof(JsonPlainArrayConverter<BookLevel>))]
public record BookLevel
{
[JsonPlainArrayIndex(0)]
public string Action { get; init; } = null!;
[JsonPlainArrayIndex(1)]
public decimal Amount { get; init; }
[JsonPlainArrayIndex(2)]
public decimal Price { get; init; }
}