using System.Text.Json.Serialization;
public static void Main()
var options = new JsonSerializerOptions();
options.Converters.Add(new ArrayToObjectConverterFactory());
options.Converters.Add(new DecimalConverter());
var result = JsonSerializer.Deserialize<MessageResponseBookSnapshot>(json, options);
Console.WriteLine($@"Result:
ChannelID: {result.ChannelID}
{string.Join("\n ", result.Book.As.Select(a => $"Price: {a.Price}, Volume: {a.Volume}, Timestamp: {a.Timestamp}"))}
ChannelName: {result.ChannelName}
public class ArrayToObjectConverterFactory : JsonConverterFactory
public override bool CanConvert(Type typeToConvert)
return Attribute.IsDefined(typeToConvert, typeof(FromJsonArrayAttribute));
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
typeof(ArrayToObjectConverter<>).MakeGenericType(
BindingFlags.Instance | BindingFlags.Public,
args: new object[] { options },
public class ArrayToObjectConverter<T> : JsonConverter<T>
public ArrayToObjectConverter(JsonSerializerOptions options)
public override bool CanConvert(Type typeToConvert)
return Attribute.IsDefined(typeToConvert, typeof(FromJsonArrayAttribute));
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType != JsonTokenType.StartArray)
throw new JsonException($"Expected the start of a JSON array but found TokenType {reader.TokenType}");
var propMap = typeToConvert.GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(PropertyIndexAttribute)))
prop => prop.GetCustomAttribute<PropertyIndexAttribute>().Index,
var result = Activator.CreateInstance(typeToConvert);
if (reader.TokenType == JsonTokenType.EndArray)
else if (propMap.TryGetValue(index, out var prop))
var value = JsonSerializer.Deserialize(ref reader, prop.PropertyType, options);
prop.SetValue(result, value);
else if (reader.TokenType == JsonTokenType.StartObject)
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
throw new NotImplementedException();
public class DecimalConverter : JsonConverter<decimal>
public override bool CanConvert(Type typeToConvert)
return typeToConvert == typeof(decimal);
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> reader.TokenType switch
JsonTokenType.Number => reader.GetDecimal(),
JsonTokenType.String => decimal.Parse(reader.GetString()),
_ => throw new JsonException($"Expected a Number or String but got TokenType {reader.TokenType}")
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
throw new NotImplementedException();
public class FromJsonArrayAttribute : Attribute { }
public class PropertyIndexAttribute : Attribute
public int Index { get; set; }
public PropertyIndexAttribute(int index)
public class MessageResponseBookSnapshot
public int ChannelID { get; set; }
public BookSnapshot Book { get; set; }
public string ChannelName { get; set; }
public string Pair { get; set; }
public class BookSnapshot
public OrderbookLevel[] As { get; set; }
public OrderbookLevel[] Bs { get; set; }
public class OrderbookLevel
public decimal Price { get; set; }
public decimal Volume { get; set; }
public decimal Timestamp { get; set; }