IgnoreMissingMember setting doesn't seem to work with FSharpLu.Json deserializer

两盒软妹~` 提交于 2020-07-03 09:59:11

问题


This is a following to: deserialization issue, with json.net, in F#.

I am deserializing some JSON that has an extra, unbound property using FSharpLu.Json. Here is the code:

open System
open Newtonsoft.Json
open Microsoft.FSharpLu.Json

type r =
    {
        a: int
    }

let a =
    "{\"a\":3, \"b\":5}" 

Compact.TupleAsArraySettings.settings.MissingMemberHandling <- MissingMemberHandling.Ignore
Compact.deserialize<r> a  // doesn't work

Despite setting MissingMemberHandling.Ignore it returns a json.net error:

Could not find member 'b' on object of type 'r'. Path 'b', line 1, position 13.

Is there a way to make this work, or is it an issue with FSharpLu.Json?

Here is the fiddle: https://dotnetfiddle.net/OsVv1M

as a side note, there is another deserializer in FSharpLu.Json and I can get that code to work with it:

FSharpLu.Json.Default.Internal.DefaultSettings.settings.MissingMemberHandling <- MissingMemberHandling.Ignore
Default.deserialize<r> a

will work, but that deserializer doesn't handle discriminated unions... so I need to get the compact one to work.

When looking into the source of FSharpLu.Json, I found this:

/// Compact serialization where tuples are serialized as heterogeneous arrays
type TupleAsArraySettings =
    static member formatting = Formatting.Indented
    static member settings =
        JsonSerializerSettings(
            NullValueHandling = NullValueHandling.Ignore,

            // MissingMemberHandling is not technically needed for
            // compact serialization but it avoids certain ambiguities
            // that guarantee that deserialization coincides with the
            // default Json.Net deserialization.
            // (where 'coincides' means 'if the deserialization succeeds they both return the same object')
            // This allows us to easily define the BackwardCompatible
            // serializer (that handles both Compact and Default Json format) by reusing
            // the Compact deserializer.
            MissingMemberHandling = MissingMemberHandling.Error,
            Converters = [| CompactUnionJsonConverter(true, true) |]
        )

so they are explicitly setting the MissingMemberHandling to Error; maybe the solution is to instantiate the deserializer, change the setting and then use it.


回答1:


The serializer setting you are trying to mutate, Compact.TupleAsArraySettings.settings, is a static member, as is shown in the code:

type TupleAsArraySettings =
    static member formatting = Formatting.Indented
    static member settings =
        JsonSerializerSettings(
            NullValueHandling = NullValueHandling.Ignore,

            // MissingMemberHandling is not technically needed for
            // compact serialization but it avoids certain ambiguities
            // that guarantee that deserialization coincides with the
            // default Json.Net deserialization.
            // (where 'coincides' means 'if the deserialization succeeds they both return the same object')
            // This allows us to easily define the BackwardCompatible
            // serializer (that handles both Compact and Default Json format) by reusing
            // the Compact deserializer.
            MissingMemberHandling = MissingMemberHandling.Error,
            Converters = [| CompactUnionJsonConverter(true, true) |]
        )

Since a member is actually a member function (i.e. a method) as explained F# for fun and profit: Attaching functions to types, settings is actually (in c# terminology) a static property returning a new instance of JsonSerializerSettings each time it is invoked. To test this, we can do:

printfn "%b" (Object.ReferenceEquals(Compact.TupleAsArraySettings.settings, Compact.TupleAsArraySettings.settings)) // prints "false"

Which prints "false". Thus mutating the returned value has no effect on the behavior of Compact. A static field in the c# sense would be defined by a static let statement; if settings returned such a field, mutating its contents would have had an effect.

In any event modifying the value of Compact.TupleAsArraySettings.settings.MissingMemberHandling seems unwise as doing so would modify the behavior of of Compact.deserialize throughout your entire AppDomain in a way that might break backwards compatibility with Json.NET native serialization. As explained in the code comments above, the setting is required to make BackwardCompatible.deserialize work. But why might that be? Since Json.NET's native format for option and discriminated unions looks like:

{
  "a": {
    "Case": "Some",
    "Fields": [
      3
    ]
  }
}

We can guess that MissingMemberHandling is used to trap situations where "Case" and "Fields" are found or not, and switch from one algorithm to the other.

If you are certain you do not need to deserialize f# types in Json.NET format, it seems you should be able to use CompactUnionJsonConverter directly as follows:

let settings = JsonSerializerSettings(
    NullValueHandling = NullValueHandling.Ignore,
    Converters = [| CompactUnionJsonConverter(true, true) |]
)
let c = JsonConvert.DeserializeObject<r>(a, settings)
let json2 = JsonConvert.SerializeObject(c, Formatting.Indented, settings)

Demo fiddle here.



来源:https://stackoverflow.com/questions/62364229/ignoremissingmember-setting-doesnt-seem-to-work-with-fsharplu-json-deserializer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!