swift 访问init中的原始JSON数据(来自解码器:解码器)方法

wlp8pajw  于 2023-05-16  发布在  Swift
关注(0)|答案(2)|浏览(222)

假设我从后端返回了这样的数据结构:

struct DummyItem: Decodable {
    let number: Int
    let name: String
}

struct DummyResponse {
    let items: [DummyItem]
    let meta: String
}

我也有这个helper struct来跳过解码失败的对象:

struct OptionalObject<Base: Decodable>: Decodable {
    let value: Base?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self.value = try container.decode(Base.self)
        } catch {
            self.value = nil
        }
    }
}

我是这样用的:

extension DummyResponse: Decodable {
    enum CodingKeys: String, CodingKey {
        case items, meta
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        let nullableItems = try container.decode([OptionalObject<DummyItem>].self, forKey: .items)
        self.items = nullableItems.compactMap({ $0.value })
        self.meta = try container.decode(String.self, forKey: .meta)
    }
}

例如,当我在后端响应中收到一些损坏的DummyItem时,我仍然会得到DummyResponse,但该项目将被跳过

{
    "items": [
        {
            "number": 1,
            "name": "first"
        },
        {
            "number": 2,
            "name": "second"
        },
        {
            "number": 3,
            "name": null
        },
    ],
    "meta": "Metadata"
}

当这种情况发生时,我如何访问有问题对象的原始JSON数据?我假设这可以在OptionalObject结构的catch块中完成。这似乎是一项简单的任务(解码器必须以某种方式访问它试图解码的原始数据,对吗?)

2sbarzqh

2sbarzqh1#

Decoder包含一个userInfo字典,你可以把任何你喜欢的,它将在解码过程中访问。在解码过程中,您还可以访问当前编码键数组(decoder.codingPath)(例如["items", "number"](如果正在解码,则为“数字”)。
因此,您可以将JSON数据存储在userInfo中,并与编码路径沿着获得损坏的JSON数据。
下面的示例应该可以按照您的预期工作。

struct OptionalObject<Base: Decodable>: Decodable {
    let value: Base?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self.value = try container.decode(Base.self)
        } catch {
            print("Found corrupt data at \(decoder.codingPath)")
            print("Corrupt data \(decoder.currentlyDecodingJSON())")
            self.value = nil
        }
    }
}

extension Decoder {
    func currentlyDecodingJSON() -> Any? {
        guard let json = userInfo[CodingUserInfoKey(rawValue: "jsonDictionary")!] else {
            return nil
        }

        return jsonAtPath(codingPath, in: json)
    }

    func jsonAtPath<T: Sequence>(_ path: T, in otherJSON: Any?) -> Any? where T.Element == CodingKey {
        guard let key = path.first(where: {_ in true}) else { return otherJSON }

        if let index = key.intValue {
            guard let array = otherJSON as? [Any] else { return otherJSON }
            return jsonAtPath(codingPath.dropFirst(), in: array[index])
        }
        guard let dictionary = otherJSON as? [String: Any] else { return otherJSON }
        return jsonAtPath(codingPath.dropFirst(), in: dictionary[key.stringValue])

    }
}

/// jsonString contains the string you have provided in the question description
let jsonData = Data(jsonString.utf8)

let decoder = JSONDecoder()

let jsonDictionary = try JSONSerialization.jsonObject(with: jsonData, options: [])
decoder.userInfo[CodingUserInfoKey(rawValue: "jsonDictionary")!] = jsonDictionary

let decodedData = try decoder.decode(DummyResponse.self, from: jsonData)

以上将打印

Found corrupt data at [CodingKeys(stringValue: "items", intValue: nil), _JSONKey(stringValue: "Index 2", intValue: 2)]
Corrupt data Optional({
    name = "<null>";
    number = 3;
})
ia2d9nvy

ia2d9nvy2#

正如所描述的,这是不可能的。JSONDecoder内部不对Data进行操作。它在NSDictionary(由JSONSerialization生成)或JSONValue枚举上运行,具体取决于平台。(在新的swift-foundation中,它稍微复杂一点,但重点是相同的。)当它调用init(from:)时,底层的Data不再可用。
要做你想做的事情,你需要手动解析JSON。这并不像听起来那么难,我做了一点,包括实现类似于你所描述的事情。但它确实需要从stdlib中复制大量代码,以使内部类型成为公共类型。
考虑到你似乎只关心“意外的JSON”,而不是实际损坏的JSON(即不是有效的JSON),那么我通常更喜欢的技术是将东西解析成像JSONValue这样的结构体(这是我用一堆帮助函数构建的自定义结构体,以使其更好地使用),然后用init(json: JSONValue) throws替换Decodable。这些初始化器往往非常容易编写,并且非常容易通过错误处理等进行扩展。我没有特别地将它捆绑到一个可重用的包中。我倾向于把这些东西复制到我的项目中,并使其适应当地的问题。
如果您使用JSONDecoder的stdlib JSONValue实现(上面链接),那么您还可以使JSONDecoderImpl公开,然后您可以在需要时自动从JSONValues解码Decodables。
所有这些问题都没有简单的答案,但是如果它对您的项目很重要,那么它肯定是非常可行的。JSONValue代码很乏味,但并不复杂(与NSDictionary代码相比,这是一个真正的痛苦)。
我在我的Advanced Codable talk中涉及了其中的一些内容,并讨论了类似的主题,这可能会很有用。

相关问题