鉴于这些类型和本单元测试:
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?
我最终使用了.NETStandard2.0而不是PCL