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

时间:2018-04-13 11:00:08

标签: ios swift multi-level decodable jsondecoder

我正在尝试将数据解码为结构体。以下是我的一个数据结构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,则由于fallbackLanguage为tr,它将回退到tr

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

注意:我在this gist中添加了inamiy来编码/解码字典和数组。

5 个答案:

答案 0 :(得分:2)

我建议为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)}

答案 1 :(得分:2)

感谢@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,用于编码和解码名为{{3}的Firebase的数据如果您使用Firebase数据库或Cloud Firestore,这是最简单和最佳的解决方案

答案 2 :(得分:2)

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

斯威夫特4:

struct X: Decodable {

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

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

    init(from decoder: Decoder) throws {
        let container = try? decoder.container(keyedBy: CodingKeys.self)
        name = try container?.decodeIfPresent(String.self, forKey: .name)
        owner = try container?.decodeIfPresent(String.self, forKey: .owner)
        fallbackLanguage = try container?.decodeIfPresent(String.self, forKey: .fallbackLanguage)
        localizedValues = try container?.decodeIfPresent(Dictionary<String, Any>.self, forKey: .localizedValues)
    }
}

答案 3 :(得分:0)

使用您的json,您可以创建以下结构

SELECT 
Col1, 
Col2, 
Col3,
coalesce(Col3, Col2) AS Col4
FROM table1

并像这样使用

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)
    }
}

答案 4 :(得分:0)

如果您没有按计划进行操作,请为此库提供一个镜头:Swifty Json

它有一些内置的方法可以抽象你的需求,例如将嵌套字典Json对象转换为NSDictionary,在那里你可以提取值并在它们上面应用控制流来构建逻辑。