如何使用包含已编码值的Encodable在Swift中编码结构

klo*_*otz 5 encoding json swift codable encodable

想象一下一个数据结构,其中包含一个contents已经编码的JSON片段的值。

let partial = """
{ "foo": "Foo", "bar": 1 }
"""

struct Document {
  let contents: String
  let other: [String: Int]
}

let doc = Document(contents: partial, other: ["foo": 1])
Run Code Online (Sandbox Code Playgroud)

所需的输出

组合的数据结构应contents原样使用并编码other

{
  "contents": { "foo": "Foo", "bar": 1 },
  "other": { "foo": 1 }
}
Run Code Online (Sandbox Code Playgroud)

使用 Encodable

以下Encodable编码实现Document为JSON,但是也将其重新编码contents为字符串,这意味着它被包装在引号中,并且所有"引号都转义为\"

{
  "contents": { "foo": "Foo", "bar": 1 },
  "other": { "foo": 1 }
}
Run Code Online (Sandbox Code Playgroud)

输出量

{
  "contents": "{\"foo\": \"Foo\", \"bar\": 1}",
  "other": { "foo": 1 }
}
Run Code Online (Sandbox Code Playgroud)

怎样才能encodecontents原样通过?

Ahm*_*d F 2

您可以通过这样做来实现它:

let partial = """
{
"foo": "Foo",
"bar": 1
}
"""

// declare a new type for `content` to deal with it as an object instead of a string
struct Document {
    let contents: Contents
    let other: [String: Int]

    struct Contents: Codable {
        let foo: String
        let bar: Int
    }
}

extension Document : Encodable {
    enum CodingKeys: String, CodingKey {
        case contents
        case other
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(contents, forKey: .contents)
        try container.encode(other, forKey: .other)
    }
}

let decoder = JSONDecoder()
let contents = try decoder.decode(Document.Contents.self, from: partial.data(using: .utf8)!)

let encoder = JSONEncoder()
let doc = Document(contents: contents, other: ["foo": 1])
let result = try encoder.encode(doc)
print(String(data: result, encoding: .utf8)!)
Run Code Online (Sandbox Code Playgroud)

基本上,您可以partial先对其进行解码,然后将其解码结果传递给Document.

输出应该是:

{"other":{"foo":1},"contents":{"foo":"Foo","bar":1}}