【问题标题】:Newtonsoft deserializing Json incorrectlyNewtonsoft 错误地反序列化 Json
【发布时间】:2019-06-18 04:27:54
【问题描述】:

我遇到过 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 是有问题的类 - 它是一个公开属性 Year、Month、Week 和 DayOfWk 的 F# 类。年、月、周是整数; DayOfWeek 是 DayOfWeek 枚举。 DailyAnalysis 对象中的所有其他字段都返回正确的值。

如何解决这个问题?

请注意,如果我在 DeserializeObject 调用中不包含类型,它确实会获取正确的数据,但只是将其作为对象返回,并且转换为正确的类型非常困难(即,我不知道如何在 F# 中做到这一点)。

谁能指出我忽略的明显(甚至晦涩)的东西,或者指出其他资源?

更新:请注意,TradeDate 的构造函数采用单个 DateTime 参数。

【问题讨论】:

  • 你能分享TradeDate 类型——即minimal reproducible example吗?
  • 不能用简单的例子复现,见dotnetfiddle.net/dxeHt9
  • 我只能猜测TradeDate 构造函数的参数名称与属性名称不匹配。 Json.NET 使用参数名称将构造函数参数与属性匹配,因此如果名称不一致,则会传入默认值。请参阅dotnetfiddle.net/YSvyVy。但如果没有minimal reproducible example,我们只能猜测。
  • dbc - 我认为你的最后一点可能是答案 - 构造函数采用单个 DateTime 参数,当然,Json 无论如何都不知道那是什么。我将探索其他方式来构建它。谢谢。
  • 现在没有时间检查,但这可能是一个急切的评估问题吗?将letJsonText = ... 更改为letJsonText () = ...

标签: json f# json.net


【解决方案1】:

假设您的 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 .NETCalculate date from week number 了解如何完成。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-06
    • 1970-01-01
    • 2015-02-16
    • 1970-01-01
    • 2019-12-20
    相关资源
    最近更新 更多