【问题标题】:Decoding JSON Response with same envelope but different content解码具有相同信封但内容不同的 JSON 响应
【发布时间】:2019-10-04 15:13:33
【问题描述】:

我正在访问一个 web api,作为响应它会发送不同的有效载荷,包裹在同一个信封中,如下所示:

获取食谱列表:

{
    "status": "SUCCESS",
    "messages": [],
    "input": null,
    "output": [
        {
            "id": 1,
            "title": "Egg with bacon"
        },
        {
            "id": 2,
            "title": "Ice cream"
        }
    ]
}

获取一个配方:

{
    "status": "SUCCESS",
    "messages": [],
    "input": {"id": 1},
    "output": {
        "id": 1,
        "title": "Egg with bacon"
    }
}

错误响应:

{
    "status": "ERROR",
    "messages": ["Recipe not found"],
    "input": {"id": 4},
    "output": null
}

类别列表:

{
    "status": "SUCCESS",
    "messages": [],
    "input": null,
    "output": [
        {
            "id": 1,
            "title": "Deserts"
        },
        {
            "id": 2,
            "title": "Main Courses"
        }
    ]
}

因此,信封键始终存在。输入是键值对象或空值,消息总是字符串数组或空数组,状态是字符串。但输出可能不同。它可以是一种 Recipe 结构、Recipe 结构数组或 Category 结构。

我的问题是:我如何解码这个 json 而无需每次都为信封编写相同的解码逻辑?我想只为信封编写一次解码器,并为输出注入不同的解码器。

【问题讨论】:

  • @vadian 您的解决方案暗示output 类型可以通过status 属性确定,此处似乎并非如此。
  • @rraphael 不是通过 status 属性,而是通过 input 属性,如果为 null,则输出为数组,否则为单个元素

标签: json swift decoder decodable


【解决方案1】:

您可以创建一个可解码的结构,用于将输入和输出包装到其中。

这看起来像:

struct ResponseContainer<Input: Decodable, Output: Decodable>: Decodable {
    var status: String
    var messages: [String]
    var input: Input?
    var output: Output
}

使用这个,如果你想解码单个配方,你只需将 Recipe 结构体包装到响应容器中:

// used to decode the `input`
struct InputId: Decodable {
    var id: Int
}

// content of the `output`
struct Recipe: Decodable {
    var id: Int
    var title: String
}

try? JSONDecoder().decode(ResponseContainer<InputId, Recipe>.self, from: singleRecipeJson)

如果你想解码一个配方列表,只需对另一个结构或数组进行同样的处理:

// As the input is expected to be null, you can use a dummy struct in the wrapper.
struct Empty: Decodable {}

try! JSONDecoder().decode(ResponseContainer<Empty, [Recipe]>.self, from: multipleRecipeJson)

注意: 虚拟结构 Empty 可能没有用,因为它增加了很多复杂性,并用于解析负载的 input 属性,看起来像您发送的内容到API(所以基本上你已经知道了,可以忽略)。在这种情况下,包装器看起来像这样:

struct ResponseContainer<Output: Decodable>: Decodable {
    var status: String
    var messages: [String]
    var output: Output
}

【讨论】:

  • 在 Swift Playground 上,我单独实施了您的解决方案,但收到以下消息:Expression implicitly coerced from 'Recipe?' to 'Any'
  • 在哪一行?我在发布回复之前自己实现了它。
  • let recipe: Recipe = container?.output, recipe 是可选的,你应该这样写let recipe: Recipe? = // ...,或者只是删除类型并让swift发挥它的魔力let recipe = container?.output
  • 我尝试了let recipe: Recipe? = container?.outputlet recipe = container?.output,它仍然显示Expression implicitly coerced from 'Recipe?' to 'Any'
猜你喜欢
  • 2014-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-16
  • 2021-04-01
  • 1970-01-01
相关资源
最近更新 更多