Utf8 Json десериализовать в тип на основе поля маркера - PullRequest
2 голосов
/ 09 апреля 2020

С Json.NET - Newtonsoft Я успешно использовал десериализаторы с заказным контрактом и json конвертер для выбора десериализатора на основе тега (в случае ниже ev).

В итоге Я чтобы добиться того же с Utf8Json, полные сведения приведены ниже:

// Stocks TRADE:
{
    "ev": "T",              // Event Type
    "sym": "MSFT",          // Symbol Ticker
    "x": "4",               // Exchange ID
    "i": 12345,             // Trade ID
    "z": 3,                 // Tape ( 1=A 2=B 3=C)
    "p": 114.125,           // Price
    "s": 100,               // Trade Size
    "c": [0, 12],           // Trade Conditions
    "t": 1536036818784      // Trade Timestamp ( Unix MS )
}

// Stocks QUOTE:
{
    "ev": "Q",              // Event Type
    "sym": "MSFT",          // Symbol Ticker
    "bx": "4",              // Bix Exchange ID
    "bp": 114.125,          // Bid Price
    "bs": 100,              // Bid Size
    "ax": "7",              // Ask Exchange ID
    "ap": 114.128,          // Ask Price
    "as": 160,              // Ask Size
    "c": 0,                 // Quote Condition
    "t": 1536036818784      // Quote Timestamp ( Unix MS )
}

// Stocks Aggregate:
{
    "ev": "AM",             // Event Type ( A = Second Agg, AM = Minute Agg )
    "sym": "MSFT",          // Symbol Ticker
    "v": 10204,             // Tick Volume
    "av": 200304,           // Accumulated Volume ( Today )
    "op": 114.04,           // Todays official opening price
    "vw": 114.4040,         // VWAP (Volume Weighted Average Price)
    "o": 114.11,            // Tick Open Price
    "c": 114.14,            // Tick Close Price
    "h": 114.19,            // Tick High Price
    "l": 114.09,            // Tick Low Price
    "a": 114.1314,          // Tick Average / VWAP Price
    "s": 1536036818784,     // Tick Start Timestamp ( Unix MS )
    "e": 1536036818784,     // Tick End Timestamp ( Unix MS )
}

И текстовый поток, который может состоять из любого из указанных выше типов:

[{"ev":"A","sym":"DAL","v":1,"av":1, ...snipped...},{"ev":"T","sym":"MSFT","p":114.11,"x":"4","s":67,"t":1586418423607, ...snipped... }]

И десериализовать до:

class Message
{
   List<Trade> Trades { get; set; }
   List<Quote> Quotes { get; set; }
   List<Aggregate> Aggs { get; set; }
}

В настоящее время я делаю это, и это работает, но сомнения столь же эффективны, как прямая десериализация в POCO:

var array = JsonSerializer.Deserialize<dynamic>(@"[{""ev"":""A"",""sym"":""AAL"",""v"":1500,""av"":119037385,""op"":12.64,""vw"":13.1,""o"":13.1,""c"":13.1,""h"":13.1,""l"":13.1,""a"":12.6655,""z"":500,""s"":1586472438000,""e"":1586472439000},{""ev"":""A"",""sym"":""AAL"",""v"":6000,""av"":119043385,""op"":12.64,""vw"":13.1,""o"":13.1,""c"":13.1,""h"":13.1,""l"":13.1,""a"":12.6655,""z"":1000,""s"":1586472439000,""e"":1586472440000},{""ev"":""A"",""sym"":""AAL"",""v"":3000,""av"":119046385,""op"":12.64,""vw"":13.11,""o"":13.11,""c"":13.11,""h"":13.11,""l"":13.11,""a"":12.6655,""z"":1000,""s"":1586472440000,""e"":1586472441000}]");

foreach(var item in array)
{
    if(item["ev"]=="A")
    {
        var aggregate = new OpenHighLowClose(
            DateTimeOffset.FromUnixTimeMilliseconds((long)item["s"]),
            (decimal)item["op"],
            (decimal)item["h"],
            (decimal)item["l"],
            (decimal)item["c"],
            (decimal)item["v"]);
    }
    else if(item["ev"=="AM"]) { }
    else if(item["ev" == "T"]) { }
    else if(item["ev" == "Q"]) { }
}

Что является эквивалентом * JsonConverter 1028 *. net in Utf8 Json, поэтому я могу переключать десериализатор на основе поля EV (и связанного строкового значения T, A, AM или Q) ?

Просто добавлю, я иду от JSON и хочу OpenHighLowClose POCO, который я затем отправлю через пакет сообщений ... Есть ли способ пропустить этот промежуточный шаг?

Ответы [ 2 ]

2 голосов
/ 16 апреля 2020

Если вы можете предположить, что поле ev всегда на первом месте в объекте, можно обойтись с минимальными выделениями и минимальным дополнительным анализом. В netcoreapp3.0 и более поздних версиях System.Text.Json поддерживает пользовательских преобразователей типов , и можно написать код, подобный следующему:

[JsonConverter(typeof(MessageConverter))]
class Message
{
   List<Trade> Trades { get; set; }
   List<Quote> Quotes { get; set; }
   List<Aggregate> Aggs { get; set; }
}

public class MessageConverter : JsonConverter<Message>
{
    public override bool CanConvert(Type typeToConvert) =>
        typeof(Message).IsAssignableFrom(typeToConvert);

    public override Message Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartArray)
            throw new JsonException();

        var message    = new Message();
        message.Trades = new List<Trade>();
        message.Quotes = new List<Quotes>();
        message.Aggs   = new List<Aggs>();

        while(Expect(ref reader, JsonTokenType.StartObject, JsonTokenType.EndArray))
        {
            Expect(ref reader, JsonTokenType.PropertyName);
            var propertyName = reader.GetString();
            if (propertyName != "ev")
                throw new JsonException();

            switch(ExpectString(ref reader))
            {
            default:
                throw new JsonException();
            case "T":
                var trade = new Trade();
                while(Expect(ref reader, JsonTokenType.PropertyName, JsonTokenType.EndObject))
                switch(reader.GetString())
                {
                default:    throw new JsonException();
                case "sym": trade.Symbol = ExpectString (ref reader); break;
                case "p":   trade.Price  = ExpectDecimal(ref reader); break;
                ...
                }
                message.Trades.Add(trade);
                break;
            ...
            }
        }

        return message;
    }

    public override void Write(Utf8JsonWriter writer, Message message, JsonSerializerOptions options) =>
        throw new NotSupportedException();

    private void Expect(ref Utf8JsonReader reader, JsonTokenType t)
    {
        reader.Read();
        if (reader.TokenType != t)
            throw new JsonException();
    }

    private string ExpectString(ref Utf8JsonReader reader)
    {
        Expect(ref reader, JsonTokenType.String);
        return reader.GetString();
    }

    private decimal ExpectDecimal(ref Utf8JsonReader reader)
    {
        Expect(ref reader, JsonTokenType.Number);
        return reader.GetDecimal();
    }

    private bool Expect(ref Utf8JsonReader reader, JsonTokenType a, JsonTokenType b)
    {
        reader.Read();
        if (reader.TokenType == a) return true ;
        if (reader.TokenType == b) return false ;
        throw new JsonException();
    }
}

Если по какой-либо причине вы не можете использовать netcoreapp3.x , тогда вам нужно будет для go JsonSerializer для внешнего Message и напрямую запустить код в методе Read. Если вы готовы потратить немного циклов, чтобы избежать десериализации внутренних объектов вручную, тогда примените атрибуты JsonPropertyName к именам своих свойств

struct Trade
{
    [JsonPropertyName("sym")]
    public string  Symbol { get; set; }
    [JsonPropertyName("p")]
    public decimal Price { get; set; }
    ...
}

и переписайте внешний l oop Read такой метод:

        while (Expect(ref reader, JsonTokenType.StartObject, JsonTokenType.EndArray))
        {
            // save reader state to peek the object type
            var copy = reader;

            Expect(ref copy, JsonTokenType.PropertyName);
            var propertyName = copy.GetString();
            if (propertyName != "ev")
                throw new JsonException();

            switch(ExpectString(ref copy))
            {
            default:
                throw new JsonException();
            case "T":
                message.Trades.Add(JsonSerializer.Deserialize<Trade>(ref reader));
                break;
            //...
            }
        }
0 голосов
/ 16 апреля 2020

Как насчет сообщения, которое выглядит примерно так:

class Message
{
   List<IStockItem> Items { get; set; }
}

IStockItem - это интерфейс, а классы типа Trade, Quote и Aggregate наследуются от IStockItem.

В вашем пользовательском JsonConverter's Прочитайте Json, вы поставите свои условия для сериализации на основе значения ev

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return string.Empty;
        }
        else if (reader.TokenType == JsonToken.String)
        {
            return serializer.Deserialize(reader, objectType);
        }
        else
        {
            JObject obj = JObject.Load(reader);
            if (obj["ev"] != "A")
                return // Logic for A
            else if (obj["ev"] != "T")
                return // Logic for T
            else
                return serializer.Deserialize(reader, objectType);
        }
    }
...