Swift 可编码:将 nil 编码为空对象

Swift Encodable: encode nil as an empty object

如何将 nil 属性 编码为空 JSON 对象?

struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil
}

struct Bar: Encodable {
    let number: Int
}

let data = try! JSONEncoder().encode(Foo())

print(String(data: data, encoding: .utf8)!)

打印出来:

"{"id":7}"

我想要的是:

"{"id":7, "bar":{}}"

bar = nil

时,您可以向 encoder 引入一个没有属性的空结构
struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil
    
    enum CodingKeys : String, CodingKey {
        case id
        case bar
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        
        if let bar = bar {
            try container.encode(bar, forKey: .bar)
        }
        else {
            try container.encode(Empty(), forKey: .bar)
        }
    }
}

struct Bar: Encodable {
    let number: Int
}

struct Empty: Encodable {
}

Foo 实现自定义 encode(to:),如果 Bar 为 nil

,则使用空字典
struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil

    enum CodingKeys: String, CodingKey {
        case id, bar
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        switch bar {
        case .some(let value):
            try container.encode(value, forKey: .bar)
        case .none:
            try container.encode([String: Bar?](), forKey: .bar)
        }
    }
}

不确定您为什么需要这个,因为编码为解码失败的形式通常不是这样。

尽管如此,如果您发现自己在多个地方需要这种逻辑,您可以使用这种功能扩展 KeyedEncodingContainer

extension KeyedEncodingContainer {
    mutating func encodeOptional<T: Encodable>(_ value: T?, forKey key: Self.Key) throws {
        if let value = value { try encode(value, forKey: key) }
        else { try encode([String:String](), forKey: key) }
    }
}

,然后在Foo中实现encode(to:)方法:

struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil
    
    enum CodingKeys: String, CodingKey {
        case id
        case bar
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encodeOptional(bar, forKey: .bar)
    }
}

如果您发现自己需要为其他类型的 nil 值编码空 JSON 对象,您还可以使用类似的 encodeOptional 方法扩展 UnkeyedDecodingContainerSingleValueDecodingContainer容器数量。