使用 Swift 将多级 JSON 解码为 iOS 中的结构 4

Decoding multi-level JSON into structs in iOS with Swift 4

我正在尝试将我的数据解码为结构。这是我的数据结构之一的示例 JSON:

{
    "name": "abc",
    "owner": "bcd",
    "fallbackLanguage": "tr",
    "localizedValues": {
        "en": {
            "description": "Lorem Ipsum Dolor Sit Amet"
        },
        "de": {
            "description": "Sed Do Eiusmod Tempor Incididunt"
        },
        "tr": {
            "description": "Consectetur Adipisicing Elit"
        }
    }
}

这个 JSON 对象的结构是:

struct X {
  let name: String
  let owner: String
  let fallbackLanguage: String

  let description: String
}

解码 nameownerfallbackLanguage 不是问题,并且已经完成。这是当前的 CodingKeyinit(from:)

struct CodingKeys: CodingKey {
    var stringValue: String
    init?(stringValue: String) {
      self.stringValue = stringValue
    }

    var intValue: Int?
    init?(intValue: Int) {
      self.intValue = intValue
      self.stringValue = "\(intValue)"

    }

    static func makeKey(name: String) -> CodingKeys {
      return CodingKeys.init(stringValue: name)!
    }
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    owner = try container.decode(String.self, forKey: CodingKeys.makeKey(name: "owner"))
    name = try container.decode(String.self, forKey: CodingKeys.makeKey(name: "name"))
    fallbackLanguage = try container.decode(String.self, forKey: CodingKeys.makeKey(name: "fallbackLanguage"))

    // FIXME: decode localizedValues into `description`
}

问题在于解码 description,因为它保存在多级字典中,并且它的值会因设备区域设置而改变。

在此示例中,如果设备区域设置不是 endetr,它将回退到 tr,因为 fallbackLanguage 是 tr

任何帮助和建议都会很棒。谢谢。

注意:我包含了this gist by inamiy来编码/解码字典和数组。

我建议为 localizedValues 创建一个结构并将值解码到字典中。然后获取当前的fallbackLanguage,从字典中获取对应的描述。不需要自定义 CodingKeys 和初始化程序。

let jsonString = """
{    "name": "abc",
    "owner": "bcd",
    "fallbackLanguage": "tr",
    "localizedValues": {
        "en": { "description": "Lorem Ipsum Dolor Sit Amet"},
        "de": { "description": "Sed Do Eiusmod Tempor Incididunt"},
        "tr": { "description": "Consectetur Adipisicing Elit"}
    }
}
"""

struct X : Decodable {
    let name: String
    let owner: String
    let fallbackLanguage: String

    let localizedValues: [String:LocalizedValue]
}

struct LocalizedValue : Decodable {
    let description : String
}

let data = Data(jsonString.utf8)
do {
    let result = try JSONDecoder().decode(X.self, from: data)
    let fallBackLanguage = result.fallbackLanguage
    let fallBackLanguageDescription = result.localizedValues[fallBackLanguage]?.description
    print(fallBackLanguageDescription)

} catch { print(error)}

感谢@AndyObusek,我了解到有 nestedContainers

这是我的问题的最终解决方案:

    // get localizedValues as nested container
    let localizedValues = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.makeKey(name: "localizedValues")!)

    var localizations: KeyedDecodingContainer<CodingKeys>

    // if device locale identifier exists in the localizedValues container as a key get it as nested container, else get for fallbackLanguage.

    if localizedValues.contains(CodingKeysmakeKey(name: Locale.current.identifier)!) {
      localizations = try localizedValues.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.makeKey(name: Locale.current.identifier))
    } else {
      localizations = try localizedValues.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.makeKey(name: fallbackLanguage))
    }

    // set description
    description = try localizations.decode(String.self, forKey: CodingKeys.makeKey(name: "description"))

编辑:

我的数据是 Cloud Firestore 数据,我发现了一个由 alickbass 创建的用于编码和解码数据的很棒的 POD for/from 名为 CodableFirebase 的 Firebase 如果您使用 Firebase 数据库或 Cloud Firestore

,这是最简单和最好的解决方案

用你的json你可以创建以下结构

struct LocalizedJSONObject: Codable {
    let name, owner, fallbackLanguage: String?
    let localizedValues: LocalizedValues?
}

struct LocalizedValues: Codable {
    let en, de, tr: De?
}

struct De: Codable {
    let description: String?
}

// MARK: Convenience initializers

extension LocalizedJSONObject {
    init(data: Data) throws {
        self = try JSONDecoder().decode(LocalizedJSONObject.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func jsonData() throws -> Data {
        return try JSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

extension LocalizedValues {
    init(data: Data) throws {
        self = try JSONDecoder().decode(LocalizedValues.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func jsonData() throws -> Data {
        return try JSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

extension De {
    init(data: Data) throws {
        self = try JSONDecoder().decode(De.self, from: data)
    }

    init(_ json: String, using encoding: String.Encoding = .utf8) throws {
        guard let data = json.data(using: encoding) else {
            throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
        }
        try self.init(data: data)
    }

    init(fromURL url: URL) throws {
        try self.init(data: try Data(contentsOf: url))
    }

    func jsonData() throws -> Data {
        return try JSONEncoder().encode(self)
    }

    func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
        return String(data: try self.jsonData(), encoding: encoding)
    }
}

然后像这样使用

let localizedJSONObject = try LocalizedJSONObject(json)

您可以使用 this extension 进行编码和解码 Array<Any>Dictionary<String: Any>

Swift 4:

struct X: Decodable {

    let name: String?
    let owner: String?
    let fallbackLanguage: String?
    let localizedValues: Dictionary<String, Any>?

    enum CodingKeys: String, CodingKey {
        case name, owner, fallbackLanguage, localizedValues
    }
}

如果你的时间不紧,试试这个图书馆:Swifty Json

它有一些内置方法可以抽象化您的需求,例如将嵌套字典 Json 对象转换为 NSDictionary,您可以在其中提取值并对其应用控制流以构建一个逻辑。