如何用Thoth.Json解码"{}"并实现Status类型反序列化?
问题描述
对接一个提供get_status端点的API,规则如下:
- 进程处于就绪状态时,返回JSON空对象
{} - 未就绪时,返回包含原因的JSON对象
用F#联合类型建模状态:
type NotReady = { a: int; b: string } // 示例结构,实际根据API定义调整 type Status = | Ready | NotReady of NotReady
尝试过两种解码方案但都存在问题:
- 错误地将空对象
{}当作字符串"{}"解码,导致Decode.fromString Decode.string "{}"报错:Error at: `$` Expecting a string but instead got: {} - 使用可选字段解码的方案会将不符合预期的JSON(比如仅含部分字段的)错误地解码为
Ready,无法准确校验数据合法性。
解决方案
核心思路是先判断JSON是否为空对象,再尝试解码NotReady结构,确保非法JSON会触发解码失败。
步骤1:实现NotReady类型的解码器
为NotReady定义强类型解码器,使用必填字段确保数据完整性:
module NotReady = let decoder = Decode.object (fun get -> { a = get.Required.Field "a" Decode.int b = get.Required.Field "b" Decode.string })
步骤2:实现Status联合类型的解码器
通过Decode.oneOf组合两个解码器,优先匹配空对象,再匹配NotReady结构:
module Status = let decoder: Decoder<Status> = // 解码空对象为Ready状态 let emptyObjectDecoder = Decode.object (fun get -> if List.isEmpty get.Fields then Decode.succeed Ready else Decode.fail "Expected empty object for Ready status") // 解码NotReady状态 let notReadyDecoder = NotReady.decoder |> Decode.map NotReady // 先尝试空对象解码,失败则尝试NotReady解码 Decode.oneOf [ emptyObjectDecoder; notReadyDecoder ]
测试验证
let ready_json = "{}" let not_ready_json = """{"a": 1, "b": "hello"}""" let unexpected_json = """{"b":"not supported to happen"}""" // 就绪状态解码 ready_json |> Decode.fromString Status.decoder // 输出:Ok Ready // 未就绪状态解码 not_ready_json |> Decode.fromString Status.decoder // 输出:Ok (NotReady { a = 1; b = "hello" }) // 非法JSON解码 unexpected_json |> Decode.fromString Status.decoder // 输出:Error "Error at: `$.a`\nExpecting a field named 'a' but instead got object with fields: [\"b\"]"
说明
- 空对象解码器通过检查
get.Fields是否为空,确保只有严格的{}才会被解码为Ready NotReady解码器使用Required.Field强制校验字段存在性,非法结构会直接解码失败,避免误判为Ready- 通过
Decode.oneOf的顺序,优先匹配就绪状态的空对象,再处理未就绪的复杂结构
内容的提问来源于stack exchange,提问作者Chechy Levas
相关产品推荐
相关产品推荐

