[Fork] [Fork] https://stackoverflow.com/questions/72260232/deserializing-array-of-price-quantity

CSharp · Console

/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters;
using System.IO;
using System.Data;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.Serialization;

using System.Text.Json;
using System.Text.Json.Serialization;

#nullable enable

using NUnit.Framework; // Throws NUnit.Framework.AssertionException

//https://stackoverflow.com/questions/72260232/deserializing-array-of-price-quantity

public sealed class JsonNumberHandlingPlainArrayConverter<T> : JsonPlainArrayConverter<T> where T : new()
{
	protected override JsonSerializerOptions CustomizePropertyOptions(PropertyInfo info, JsonSerializerOptions options)
	{
		var copy = new JsonSerializerOptions(options);
		copy.NumberHandling = JsonNumberHandling.AllowReadingFromString;
		return copy;
	}
}

// JsonPlainArrayIndexAttribute and  JsonPlainArrayConverter<T> adapted from this answer https://stackoverflow.com/a/71649515/3744182
// By https://stackoverflow.com/users/15283686/yueyinqiu
// To https://stackoverflow.com/questions/71649101/system-text-json-fails-to-deserialize-listlistobject

[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 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, 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")] List<BookLevel> Asks,
    [property: JsonPropertyName("bids")] List<BookLevel> Bids,
    [property: JsonPropertyName("prev_sequence")] long PreviousSequence
    //[property: JsonPropertyName("changes")] IEnumerable<Changes> Changes
);

//Compilation error:
//[JsonConverter(typeof(JsonStringEnumMemberConverter))]
public enum DepthType
{
    [EnumMember(Value = "snapshot")]
    Snapshot,

    [EnumMember(Value = "update")]
    Update
}

[JsonConverter(typeof(JsonNumberHandlingPlainArrayConverter<BookLevel>))]
//ERROR:
//[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);

    class TestClass
    {
		public static void Test()
		{
			var json = GetJson();
			
			var options = new JsonSerializerOptions
			{
				//PropertyNameCaseInsensitive = true,
				//NumberHandling = JsonNumberHandling.AllowReadingFromString,
			};
			
			var model = JsonSerializer.Deserialize<Depth>(json, options);
			
			model.Dump();

			Assert.AreEqual(1, model?.Asks.Count);
			Assert.AreEqual(1, model?.Bids.Count);
			Assert.AreEqual("0.09000000", model?.Asks[0].Price);
			Assert.AreEqual("0.20000000", model?.Asks[0].Quantity);
			
		}
		
		static string GetJson() => @"{
        ""type"":""snapshot"",
        ""pair"":""BTC-USDT"",
        ""sequence"":9,
        ""bids"":[
            [
                ""0.08000000"",
                ""0.10000000""
            ]
        ],
        ""asks"":[
            [
                ""0.09000000"",
                ""0.20000000""
            ]
        ]
    }
";
	}

	public class Program
	{
		public static void Main()
		{
			Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
			Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
			Console.WriteLine();

			try
			{
				TestClass.Test();
			}
			catch (Exception ex)
			{
				Console.WriteLine("Failed with unhandled exception: ");
				Console.WriteLine(ex);
				throw;
			}
		}
	}
;