将JSON解码为对象或数组的Swift错误处理技术

i1icjdpr  于 5个月前  发布在  Swift
关注(0)|答案(1)|浏览(52)

有一些JSON格式,其中的属性有时是字典,有时是数组。
关于这个问题有几个问题。这个问题是关于良好的错误处理。这个问题从Hot to decode JSON data that could and array or a single element in Swift?的可接受答案结束的地方开始。

变量数据

如果只有一个值,则名称数据以字典的形式出现:

{
"id": "item123",
"name": {
    "@language": "en",
    "@value": "Test Item"
}
}

字符串
如果有两个或多个值,则名称数据将以数组的形式出现:

{
"id": "item123",
"name": [
    {
        "@language": "en",
        "@value": "Test Item"
    },
    {
        "@language": "de",
        "@value": "Testartikel"
    }
]
}


这是我的解决方案,如果Decodable结构与要解码的JSON数据匹配,它就可以正常工作。

struct Item: Codable {
    var id: String
    var name: [LocalizedString]

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

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)

        if let singleName = try? container.decode(LocalizedString.self, forKey: .name) {
            name = [singleName]
        } else {
            name = try container.decodeIfPresent([LocalizedString].self, forKey: .name) ?? []
        }
    }
}

struct LocalizedString: Codable {
    var language: String
    var value: String

    enum CodingKeys: String, CodingKey {
        case language = "@language"
        case value = "@value"
    }
}


(this与其他地方接受的答案相匹配)
它的缺点是,解码错误消息在这种方法中不再有用。
假设像这样的意外数据(@language丢失):

{
    "id": "item123",
    "name": {
        "@value": "Test Item"
    }
}


我收到一条错误消息
错误:typeMismatch(Swift.Array,Swift.DecodingError.Context(codingPath:[CodingKeys(stringValue:“name”,intValue:nil)],debugDescription:“期望解码Array,但却找到了字典。",underlyingError:nil))
而不是正确的错误消息,关键语言丢失。
正确的错误消息将与问号一起被丢弃,

if let singleName = try?


那么有没有一种方法可以先检查它是否是一个数组,然后使用拟合

container.decodeIfPresent(LocalizedString.self, forKey: .name)


container.decodeIfPresent([LocalizedString].self, forKey: .name)


在任何情况下都能得到一个好的错误消息?
我的真实的世界数据比这个简单的例子嵌套得多,所以当错误消息不合适时,很难找到错误。
下面是这个问题的一个完整的可测试的例子:

import XCTest

class ItemDecodingTests: XCTestCase {

    func testDecodeItemWithSingleLocalizedString() throws {
        let jsonData = test_json_data_single.data(using: .utf8)!
        let item = try JSONDecoder().decode(Item.self, from: jsonData)
        XCTAssertNotNil(item)
    }

    func testDecodeItemWithMultipleLocalizedStrings() throws {
        let jsonData = test_json_data_array.data(using: .utf8)!
        let item = try JSONDecoder().decode(Item.self, from: jsonData)
        XCTAssertNotNil(item)
    }
    
    func testDecodeItemWithMissingLanguageProperty() throws {
        let jsonData = test_json_data_missing_language.data(using: .utf8)!
        XCTAssertThrowsError(try JSONDecoder().decode(Item.self, from: jsonData)) { error in
            print("Error: \(error)")
        }
    }
}

// Test JSON Data
let test_json_data_single = """
{
    "id": "item123",
    "name": {
        "@language": "en",
        "@value": "Test Item"
    }
}
"""

let test_json_data_array = """
{
    "id": "item123",
    "name": [
        {
            "@language": "en",
            "@value": "Test Item"
        },
        {
            "@language": "de",
            "@value": "Testartikel"
        }
    ]
}
"""

let test_json_data_missing_language = """
{
    "id": "item123",
    "name": {
        "@value": "Test Item"
    }
}
"""

struct Item: Codable {
    var id: String
    var name: [LocalizedString]

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

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)

        if let singleName = try? container.decode(LocalizedString.self, forKey: .name) {
            name = [singleName]
        } else {
            name = try container.decodeIfPresent([LocalizedString].self, forKey: .name) ?? []
        }
    }
}

struct LocalizedString: Codable {
    var language: String
    var value: String

    enum CodingKeys: String, CodingKey {
        case language = "@language"
        case value = "@value"
    }
}

xmd2e60i

xmd2e60i1#

您没有得到预期的错误消息,因为您故意忽略了try?的错误。
更好的方法是catch,并在解码数组之前显式地重新抛出keyNotFound错误

struct Item: Codable {
    var id: String
    var name: [LocalizedString]
    
    enum CodingKeys: String, CodingKey {
        case id
        case name
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        
        do {
            let singleName = try container.decode(LocalizedString.self, forKey: .name)
            name = [singleName]
        } catch DecodingError.keyNotFound(let key, let context) {
            throw DecodingError.keyNotFound(key, context)
        } catch {
            name = try container.decodeIfPresent([LocalizedString].self, forKey: .name) ?? []
        }
    }
}

字符串

相关问题