Newtonsoft错误地反序列化Json

时间:2019-06-15 18:49:13

标签: json f# json.net

我遇到过一种情况,其中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 个答案:

答案 0 :(得分:2)

假设您的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 here

那么,您有什么选择?首先,您可以修改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 here

第二,如果您不能修改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)

演示小提琴#3 here

注意:

  1. 您的问题不包括TradeDate的代码,尤其是用于在DateTime和年/月/周的月份/星期几之间进行转换的代码。事实证明这是不平凡的,所以我没有在答案中包括它。有关如何执行的操作,请参见 Calculate week of month in .NET Calculate date from week number

  2. 有关Json.NET如何为具有多个构造函数的类型选择要调用哪个构造函数的详细信息,请参见 How does JSON deserialization in C# work