Newtonsoft неправильно десериализует Json - PullRequest
0 голосов
/ 15 июня 2019

Я столкнулся со случаем, когда Newtonsoft принимает совершенно корректный текст JSON, но неправильно десериализует его.У меня есть объект, который содержит встроенный класс, состоящий из членов Year, Month, Week и DayOfWk.JSON выглядит следующим образом:

 "openDate": {
  "Year": 1997,
  "Month": 12,
  "Week": 5,
  "DayOfWk": 5
 },

Но данные, которые возвращаются после десериализации, равны Year = 1, Month = 1, Week = 1 и DayOfWk = 1, независимо от входного JSON.

Вот код (он написан на F #, но должен быть легко читаемым):

  let jsonText = File.ReadAllText( @"..\..\..\..\Dependencies\ADBE.dat")
  let dailyData = JsonConvert.DeserializeObject<DailyAnalysis[]>(jsonText)

DailyAnalysis определяется как:

type DailyAnalysis = {
openDate: TradeDate
openPrice: Decimal
closeDate: TradeDate
closePrice: Decimal
gainPercentage: Decimal
}

TradeDate - рассматриваемый класс - этоэто класс F #, который предоставляет свойства Year, Month, Week и DayOfWk.Год, Месяц и Неделя - это целые числа;DayOfWeek - это перечисление DayOfWeek.Все остальные поля в объектах DailyAnalysis возвращаются с правильными значениями.

Как можно решить эту проблему?

Обратите внимание, что если я не включу тип в вызов DeserializeObject, онполучает правильные данные, но просто возвращает их как объект, и преобразование в правильный тип очень сложно (т. е. я не знаю, как это сделать в F #).

Может кто-нибудь указать на что-тоочевидно (или даже неясно) я упускаю из виду или указываете мне на другие ресурсы?

Обновление: Обратите внимание, что конструктор для TradeDate принимает один аргумент DateTime.

1 Ответ

2 голосов
/ 17 июня 2019

Если предположить, что ваш TradeDate является неизменным (как обычно это происходит в f #), то Json.NET может десериализовать такой тип, найдя один конструктор, который параметризуется, а затем вызовет его, сопоставив аргументы конструктора со свойствами JSONимя, по модулю дела.Аргументы, которые не совпадают, получают значение по умолчанию.Если TradeDate фактически принимает один DateTime в качестве ввода, вы получите поведение, которое вы видите.

Например, если мы возьмем упрощенную версию, например, такую:

type TradeDate(date : DateTime) = 
    member this.Year = date.Year
    member this.Month = date.Month
    member this.DayOfMonth = date.Day

А затем выполните обход в обход с помощью Json.NET следующим образом:

let t1 = new TradeDate(new DateTime(1997, 12, 25))
let json1 = JsonConvert.SerializeObject(t1)
let t2 = JsonConvert.DeserializeObject<TradeDate>(json1)
let json2 = JsonConvert.SerializeObject(t2)

printfn "\nResult after round-trip:\n%s" json2

В результате получается:

{"Year":1,"Month":1,"DayOfMonth":1}

Именно это вы и видите.Демо-скрипка № 1 здесь .

Итак, какие у вас варианты?Во-первых, вы можете изменить TradeDate, чтобы получить необходимый конструктор, и пометить его как JsonConstructor.Он может быть закрытым, если применяется атрибут:

type TradeDate [<JsonConstructor>] private(year : int, month : int, dayOfMonth: int) = 
    member this.Year = year
    member this.Month = month
    member this.DayOfMonth = dayOfMonth

    new(date : DateTime) = new TradeDate(date.Year, date.Month, date.Day)

Демонстрационная скрипка # 2 здесь .

Во-вторых, если вы не можете изменить TradeDate или добавитьАтрибуты Json.NET можно добавить для него custom JsonConverter:

[<AllowNullLiteral>] type private TradeDateDTO(year : int, month : int, dayOfMonth : int) =
    member this.Year = year
    member this.Month = month
    member this.DayOfMonth = dayOfMonth

type TradeDateConverter () =
    inherit JsonConverter()

    override this.CanConvert(t) = (t = typedefof<TradeDate>)

    override this.ReadJson(reader, t, existingValue, serializer) = 
        let dto = serializer.Deserialize<TradeDateDTO>(reader)
        match dto with
        | null -> raise (new JsonSerializationException("null TradeDate"))
        | _ -> new TradeDate(new DateTime(dto.Year, dto.Month, dto.DayOfMonth)) :> Object

    override this.CanWrite = false

    override this.WriteJson(writer, value, serializer) = 
        raise (new NotImplementedException());

и десериализовать следующим образом:

let converter = new TradeDateConverter()
let t2 = JsonConvert.DeserializeObject<TradeDate>(json1, converter)

Demo Fiddle #3 здесь .

Примечания:

  1. Ваш вопрос не включает код для TradeDate, в частности код для преобразования между DateTime и представление года / месяца / недели месяца / дня недели.Это оказывается немного нетривиальным, поэтому я не включил его в ответ;см. Расчет недели месяца в .NET и Расчет даты по номеру недели , как это можно сделать.

  2. Подробнее о том, как Json.NET выбирает какой конструктор вызывать для типа с несколькими конструкторами, см. Как работает десериализация JSON в C # .

...