TypeConverter适用于Newtonsoft.Json,但不适用于JsonConverter? (F#)

时间:2017-10-06 04:11:04

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

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

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)收到此异常:

  

----&GT; Newtonsoft.Json.JsonSerializationException:无法转换   字符串&#39; D1&#39;字典键类型   &#39; FSharpTests.Deserialization + DiscUnion&#39 ;.创建一个   TypeConverter将字符串转换为键类型对象。路径   &#39; Value.Baz.D1&#39;,第1行,第82位.----&gt;   Newtonsoft.Json.JsonSerializationException:转换值时出错   &#34; D1&#34;输入&#39; FSharpTests.Deserialization + DiscUnion&#39;。路径   &#39; Value.Baz.D1&#39;,第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)

并且效果很好,但是,TypeConverter类型并不存在于PCL配置文件中(我猜是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())

但这不起作用,我仍然得到以前的例外:

  

----&GT; Newtonsoft.Json.JsonSerializationException:无法转换   字符串&#39; D1&#39;字典键类型   &#39; FSharpTests.Deserialization + DiscUnion&#39 ;.创建一个   TypeConverter将字符串转换为键类型对象。路径   &#39; Value.Baz.D1&#39;,第1行,第82位.----&gt;   Newtonsoft.Json.JsonSerializationException:转换值时出错   &#34; D1&#34;输入&#39; FSharpTests.Deserialization + DiscUnion&#39;。路径   &#39; Value.Baz.D1&#39;,第1行,第82位。

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

1 个答案:

答案 0 :(得分:0)

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