【问题标题】:TypeConverter working for Newtonsoft.Json, but not JsonConverter? (F#)TypeConverter 适用于 Newtonsoft.Json,但不适用于 JsonConverter? (F#)
【发布时间】:2017-10-06 04:11:04
【问题描述】:

鉴于这些类型和这个单元测试:

type DiscUnion =
    | D1
    | D2

type Foo =
    {
        Bar: int;
        Baz: Map<DiscUnion,int>;
    }

let ImportDiscUnionJson (json: string): Foo =
    Marshalling.Deserialize json

let DiscUnionExampleInJson =
    "{\"Bar\": 42, \"Baz\":" +
    "{\"D1\": 4242, " +
    "\"D2\": 424242 }}"

[<Test>]
let ``testing disc union deserialization``() =

    let deserializedDiscUnion =
        ImportDiscUnionJson
            DiscUnionExampleInJson

    Assert.That(deserializedDiscUnion, Is.Not.Null)

    Assert.That(deserializedDiscUnion.Bar,
        Is.EqualTo(42))

    Assert.That(deserializedDiscUnion.Baz.[DiscUnion.D1],
        Is.EqualTo(4242))

    Assert.That(deserializedDiscUnion.Baz.[DiscUnion.D2],
        Is.EqualTo(424242))

我从 Newtonsoft.Json 收到了这个异常(JSON.NET,我使用的是 9.0.1 版):

----> Newtonsoft.Json.JsonSerializationException : 无法转换 字符串“D1”到字典键类型 'FSharpTests.Deserialization+DiscUnion'。创建一个 TypeConverter 从字符串转换为键类型对象。小路 'Value.Baz.D1',第 1 行,第 82 位。----> Newtonsoft.Json.JsonSerializationException:转换值时出错 “D1”键入“FSharpTests.Deserialization+DiscUnion”。小路 'Value.Baz.D1',第 1 行,第 82 位。

然后我在类型中添加了一个 TypeConverter,这样:

let Construct<'T> (caseInfo: Microsoft.FSharp.Reflection.UnionCaseInfo) =
    Microsoft.FSharp.Reflection.FSharpValue.MakeUnion(caseInfo, [||]) :?> 'T
let GetUnionCaseInfoAndInstance<'T> (caseInfo: Microsoft.FSharp.Reflection.UnionCaseInfo) =
    (Construct<'T> caseInfo)
let GetAllElementsFromDiscriminatedUnion<'T>() =
    Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof<'T>)
    |> Seq.map GetUnionCaseInfoAndInstance<'T>

[<System.ComponentModel.TypeConverter(typeof<MyStringTypeConverter>)>]
type DiscUnion =
    | D1
    | D2
    override self.ToString() =
        sprintf "%A" self
    static member GetAll(): seq<DiscUnion> =
        GetAllElementsFromDiscriminatedUnion<DiscUnion>()

and private MyStringTypeConverter() =
    inherit System.ComponentModel.TypeConverter()
    override this.CanConvertFrom(context, sourceType) =
        sourceType = typedefof<string> || base.CanConvertFrom(context, sourceType)
    override this.ConvertFrom(context, culture, value) =
        match value with
        | :? string as stringValue ->
            Seq.find (fun discUnion -> discUnion.ToString() = stringValue) (DiscUnion.GetAll()) :> obj
        | _ -> base.ConvertFrom(context, culture, value)

而且效果很好,但是,PCL 配置文件中不存在 TypeConverter 类型(我猜System.ComponentModel 没有),所以我尝试使用 JsonConverter:

let Construct<'T> (caseInfo: Microsoft.FSharp.Reflection.UnionCaseInfo) =
    Microsoft.FSharp.Reflection.FSharpValue.MakeUnion(caseInfo, [||]) :?> 'T
let GetUnionCaseInfoAndInstance<'T> (caseInfo: Microsoft.FSharp.Reflection.UnionCaseInfo) =
    (Construct<'T> caseInfo)
let GetAllElementsFromDiscriminatedUnion<'T>() =
    Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof<'T>)
    |> Seq.map GetUnionCaseInfoAndInstance<'T>

[<JsonConverter(typeof<MyStringTypeConverter>)>]
type DiscUnion =
    | D1
    | D2
    override self.ToString() =
        sprintf "%A" self
    static member GetAll(): seq<DiscUnion> =
        GetAllElementsFromDiscriminatedUnion<DiscUnion>()

and private MyStringTypeConverter() =
    inherit JsonConverter()

    override this.CanConvert(objectType): bool =
        objectType = typedefof<DiscUnion>

    override this.ReadJson(reader: JsonReader, objectType: Type, existingValue: Object, serializer: JsonSerializer) =
        if (reader.TokenType = JsonToken.Null) then
            null
        else
            let token =
                Newtonsoft.Json.Linq.JToken.Load(reader)
                      // not sure about the below way to convert to string, in stackoverflow it was a C# cast
                      .ToString()
            try
                DiscUnion.GetAll().First(fun discUnion -> discUnion.ToString() = token) :> Object
            with ex -> raise(new Exception(sprintf "DiscUnion case not found: %s" token, ex))

    override this.WriteJson(writer: JsonWriter, value: Object, serializer: JsonSerializer) =
        let discUnion = value :?> DiscUnion
        writer.WriteValue(discUnion.ToString())

但这不起作用,我仍然得到之前的异常:

----> Newtonsoft.Json.JsonSerializationException : 无法转换 字符串“D1”到字典键类型 'FSharpTests.Deserialization+DiscUnion'。创建一个 TypeConverter 从字符串转换为键类型对象。小路 'Value.Baz.D1',第 1 行,第 82 位。----> Newtonsoft.Json.JsonSerializationException:转换值时出错 “D1”键入“FSharpTests.Deserialization+DiscUnion”。小路 'Value.Baz.D1',第 1 行,第 82 位。

如何解决这个问题并且仍然兼容 PCL?

【问题讨论】:

    标签: .net json f# portable-class-library


    【解决方案1】:

    我最终使用 .NETStandard2.0 而不是 PCL

    【讨论】:

      猜你喜欢
      • 2020-02-11
      • 1970-01-01
      • 1970-01-01
      • 2019-07-03
      • 2020-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多