JSONSerialiser用于包含Optional属性的encodeEncodable时,nil值从编码中被省略,潜在地导致完全空的(并且漂亮而紧凑的) struct .以下是这类行为的XCTestCase分:

func test_whenNilValueIsSerialised_thenItIsOmitted() throws {

    struct Container: Codable {
        let cargo: Int?
    }

    let encoding = try XCTUnwrap(String(bytes: JSONEncoder().encode(Container(cargo: nil)), encoding: .utf8))
    XCTAssertEqual(encoding, "{}")
}

但是,如果Container是泛型,并且使用包装类型实例化为可选类型,则不会发生这种行为.以下测试用例失败:

func test_whenNilValueFromGenericIsSerialised_thenItIsOmitted() throws {

    struct Container<Cargo: Codable>: Codable {
        let cargo: Cargo
    }

    let container = Container<String?>(cargo: nil)
    let encoding = try XCTUnwrap(String(bytes: JSONEncoder().encode(container), encoding: .utf8))

    XCTAssertEqual(encoding, "{}")
}

失败错误为:

XCTAssertEquity失败:("{"Cargo":Null}")不等于("{}")

The Question:是否有合理的干净工作来解决这一问题,也许是通过为Container实现显式的encode(to:)


注:在这种情况下,以下解决方案不合适:

struct Container<Cargo: Codable>: Codable {
    let cargo: Cargo?
}

…我希望能够在许多情况下使用非可选的Cargo.

推荐答案

我认为你需要做大约in this question次.您需要判断cargo是否真的是一个可选类型,并获取包装值.然后,encodeIfPresent那个展开的值.

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    let mirror = Mirror(reflecting: cargo)
    if mirror.displayStyle == .optional {
        let unwrapped = mirror.children.first?.value as? Encodable
        try container.encodeIfPresent(unwrapped.map(AnyEncodable.init), forKey: .cargo)
    } else {
        try container.encode(cargo, forKey: .cargo)
    }
}

struct AnyEncodable: Encodable {
    let wrapped: any Encodable
    func encode(to encoder: Encoder) throws {
        try wrapped.encode(to: encoder)
    }
}

请注意,由于展开后的值是Any?类型,因此需要强制转换为Encodable,然后将其包装在具体类型(AnyEncodable)中.

你也可以把它写成KeyedEncodingContainer的扩展名:

extension KeyedEncodingContainer {
    mutating func encodeGenericIfPresent<T: Encodable>(_ x: T, forKey key: Key) throws {
        let mirror = Mirror(reflecting: x)
        if mirror.displayStyle == .optional {
            let unwrapped = mirror.children.first?.value as? Encodable
            try encodeIfPresent(unwrapped.map(AnyEncodable.init), forKey: key)
        } else {
            try encode(x, forKey: key)
        }
    }
}

如果对嵌套可选项进行编码,其中外部可选项不为零,但内部可选项为零,

JSONEncoder().encode(Container(cargo: String??.some(.none))

它仍然可以生产{"cargo": null}辆.这与非通用情况下的综合encode实现是一致的:

struct Container: Codable {
    var cargo: String??
}

Swift相关问答推荐

字符串目录不适用于SWIFT包

如何在AudioKit中实现自定义音效 node ?

从SwiftUI使用UIPageView时,同一页面连续显示两次的问题

如何避免切换视图递归onChange调用SwiftUI

如何在macOS中延迟退出应用程序的退出操作?

Swift 扩展:通过方法重载增强现有类

如何使用模型在 SwiftUI 的列表中进行搜索

使用`JSONSerialiser`时,省略通用可选项

如何隐藏集合视图中特定的单元格,以避免出现空白空间?

领域异常中的相同方法:发送到实例的无法识别的 Select 器

签署GoogleSignIn-GoogleSignIn需要开发团队

在 Swift 中实现自定义异步序列

我们如何显示关于 UITextField 是 xCode Swift 的建议?

Swift在十进制格式中失go 精度

Swift 4 Decodable - 以枚举为键的字典

由于编译器中的内部保护级别,无法访问框架 init 中的公共 struct

TabView 在切换选项卡时重置导航堆栈

来自 ObservableObject 的绑定值

如何判断 firebase 数据库值是否存在?

在这个例子中美元符号有什么作用?