使用Swift的Encodable将可选属性编码为null,无需自定义编码

fl0*_*034 10 encoding json ios swift codable

我想JSONEncoder使用struct符合Encodable协议的Swift编码可选字段.

默认设置是JSONEncoder使用encodeIfPresent方法,这意味着nil从Json中排除的值.

如何在不编写自定义encode(to encoder: Encoder)函数的情况下为单个属性覆盖此属性,我必须在其中实现所有属性的编码(如本文在"自定义编码"下建议的那样)?

例:

struct MyStruct: Encodable {
    let id: Int
    let date: Date?
}

let myStruct = MyStruct(id: 10, date: nil)
let jsonData = try JSONEncoder().encode(myStruct)
print(String(data: jsonData, encoding: .utf8)!) // {"id":10}
Run Code Online (Sandbox Code Playgroud)

Pau*_*l B 1

让我为此建议一个属性包装器。

@CodableExplicitNull

import Foundation

@propertyWrapper
public struct CodableExplicitNull<Wrapped> {
    public var wrappedValue: Wrapped?
    
    public init(wrappedValue: Wrapped?) {
        self.wrappedValue = wrappedValue
    }
}

extension CodableExplicitNull: Encodable where Wrapped: Encodable {
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch wrappedValue {
        case .some(let value): try container.encode(value)
        case .none: try container.encodeNil()
        }
    }
}

extension CodableExplicitNull: Decodable where Wrapped: Decodable {
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            wrappedValue = try container.decode(Wrapped.self)
        }
    }
}

extension CodableExplicitNull: Equatable where Wrapped: Equatable { }

extension KeyedDecodingContainer {
    
    public func decode<Wrapped>(_ type: CodableExplicitNull<Wrapped>.Type,
                                forKey key: KeyedDecodingContainer<K>.Key) throws -> CodableExplicitNull<Wrapped> where Wrapped: Decodable {
        return try decodeIfPresent(CodableExplicitNull<Wrapped>.self, forKey: key) ?? CodableExplicitNull<Wrapped>(wrappedValue: nil)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

struct Test: Codable {
    @CodableExplicitNull var name: String? = nil
}

let data = try JSONEncoder().encode(Test())
print(String(data: data, encoding: .utf8) ?? "")

let obj = try JSONDecoder().decode(Test.self, from: data)
print(obj)
Run Code Online (Sandbox Code Playgroud)

给予

{"name":null}
Test(name: nil)
Run Code Online (Sandbox Code Playgroud)