I'm using System.Text.Json
and it fails to deserialize BookLevel[]
. BookLevel is something like 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]]}}}
Back in Newtonsoft.Json
I could've marked BookLevel
as [JsonConverter(typeof(ObjectToArrayConverter<LevelEvent>))]
and use the following. How do I do that with System.Text.Json?
/// <summary>
/// Adapted from https://stackoverflow.com/questions/39461518/c-sharp-json-net-deserialize-response-that-uses-an-unusual-data-structure
/// </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;
}
}