Thoth.Json.Net 条件解析
Thoth.Json.Net conditional parsing
我有一个 JSON 文档,我正在使用 Thoth.Json.Net 解析它。该文档有一个包含一组对象的数组,每个对象都有一个“类型”属性,其值标识它们的类型。这些类型中的每一种都需要不同的解码器,因此我需要能够根据“类型”属性的值提供某种过滤器。我该怎么做?
更新:
在我上面描述的“hack”工作后,我重新使用 CE 和 decodeByType
自定义解码器,每个解码器从上面提到的 @tranquillity 中的可区分联合返回一个值。一旦我了解了所有类型,我唯一要做的就是为 Builder 指定类型:
Builder: type DecoderBuilder () =
member __.Bind((decoder:Decoder<'a>), (func:('a -> Decoder<'b>))) =
Decode.andThen func decoder
这使得使用 CE 可以轻松组合解码器,如下面的 @brianberns 所述。我为自定义解码器和 Discriminated Union 创建了一个新文件,发现我能够从 JSON 中提取更接近域模型的值(使无法表示的状态变得不可能,因为如果 JSON结构无效)。
总而言之,代码更简洁、更实用、更优雅。谢谢你的帮助。
我不是透特专家,但这就是我要做的。首先,我发现使用计算表达式组合解码器更容易:
type DecodeBuilder() =
member _.Bind(decoder, f) : Decoder<_> =
Decode.andThen f decoder
member _.Return(value) =
Decode.succeed value
member _.ReturnFrom(decoder : Decoder<_>) =
decoder
let decode = DecodeBuilder()
然后我发明了两个自定义解码器,只是作为例子,并按名称将它们放在一个地图中。一个反转字符串,一个使用 ROT13 密码解码。当然,您将在这里使用自己的自定义解码器:
let decodeReverse =
decode {
let! str = Decode.string
return str
|> Seq.rev
|> Seq.toArray
|> String
}
let decodeRot13 =
let rot13 c =
if 'a' <= c && c <= 'm' || 'A' <= c && c <= 'M' then
char (int c + 13)
elif 'n' <= c && c <= 'z' || 'N' <= c && c <= 'Z' then
char (int c - 13)
else c
decode {
let! str = Decode.string
return str
|> Seq.map rot13
|> Seq.toArray
|> String
}
let customDecoders =
Map [
"Reverse", decodeReverse
"Rot13", decodeRot13
]
那么,自定义解码就是解码“type”字段,查找对应的自定义解码器,并用它来解码“value”字段:
let decodeByType =
decode {
let! typ = Decode.field "type" Decode.string
return! Decode.field "value" customDecoders.[typ]
}
用法示例:
Decode.fromString
(Decode.array decodeByType)
"""
[
{
"type" : "Reverse",
"value" : "edcba"
},
{
"type" : "Rot13",
"value" : "qrpbqr guvf"
}
]
"""
|> printfn "%A" // Ok [|"abcde"; "decode this"|]
我把完整的程序放上去here供参考
我有一个 JSON 文档,我正在使用 Thoth.Json.Net 解析它。该文档有一个包含一组对象的数组,每个对象都有一个“类型”属性,其值标识它们的类型。这些类型中的每一种都需要不同的解码器,因此我需要能够根据“类型”属性的值提供某种过滤器。我该怎么做?
更新:
在我上面描述的“hack”工作后,我重新使用 CE 和 decodeByType
自定义解码器,每个解码器从上面提到的 @tranquillity 中的可区分联合返回一个值。一旦我了解了所有类型,我唯一要做的就是为 Builder 指定类型:
Builder: type DecoderBuilder () =
member __.Bind((decoder:Decoder<'a>), (func:('a -> Decoder<'b>))) =
Decode.andThen func decoder
这使得使用 CE 可以轻松组合解码器,如下面的 @brianberns 所述。我为自定义解码器和 Discriminated Union 创建了一个新文件,发现我能够从 JSON 中提取更接近域模型的值(使无法表示的状态变得不可能,因为如果 JSON结构无效)。
总而言之,代码更简洁、更实用、更优雅。谢谢你的帮助。
我不是透特专家,但这就是我要做的。首先,我发现使用计算表达式组合解码器更容易:
type DecodeBuilder() =
member _.Bind(decoder, f) : Decoder<_> =
Decode.andThen f decoder
member _.Return(value) =
Decode.succeed value
member _.ReturnFrom(decoder : Decoder<_>) =
decoder
let decode = DecodeBuilder()
然后我发明了两个自定义解码器,只是作为例子,并按名称将它们放在一个地图中。一个反转字符串,一个使用 ROT13 密码解码。当然,您将在这里使用自己的自定义解码器:
let decodeReverse =
decode {
let! str = Decode.string
return str
|> Seq.rev
|> Seq.toArray
|> String
}
let decodeRot13 =
let rot13 c =
if 'a' <= c && c <= 'm' || 'A' <= c && c <= 'M' then
char (int c + 13)
elif 'n' <= c && c <= 'z' || 'N' <= c && c <= 'Z' then
char (int c - 13)
else c
decode {
let! str = Decode.string
return str
|> Seq.map rot13
|> Seq.toArray
|> String
}
let customDecoders =
Map [
"Reverse", decodeReverse
"Rot13", decodeRot13
]
那么,自定义解码就是解码“type”字段,查找对应的自定义解码器,并用它来解码“value”字段:
let decodeByType =
decode {
let! typ = Decode.field "type" Decode.string
return! Decode.field "value" customDecoders.[typ]
}
用法示例:
Decode.fromString
(Decode.array decodeByType)
"""
[
{
"type" : "Reverse",
"value" : "edcba"
},
{
"type" : "Rot13",
"value" : "qrpbqr guvf"
}
]
"""
|> printfn "%A" // Ok [|"abcde"; "decode this"|]
我把完整的程序放上去here供参考