【发布时间】: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