使用 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
}
解码 name
、owner
和 fallbackLanguage
不是问题,并且已经完成。这是当前的 CodingKey
和 init(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
,因为它保存在多级字典中,并且它的值会因设备区域设置而改变。
在此示例中,如果设备区域设置不是 en
、de
或 tr
,它将回退到 tr
,因为 fallbackLanguage 是 tr
。
任何帮助和建议都会很棒。谢谢。
我建议为 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,您可以在其中提取值并对其应用控制流以构建一个逻辑。
我正在尝试将我的数据解码为结构。这是我的数据结构之一的示例 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
}
解码 name
、owner
和 fallbackLanguage
不是问题,并且已经完成。这是当前的 CodingKey
和 init(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
,因为它保存在多级字典中,并且它的值会因设备区域设置而改变。
在此示例中,如果设备区域设置不是 en
、de
或 tr
,它将回退到 tr
,因为 fallbackLanguage 是 tr
。
任何帮助和建议都会很棒。谢谢。
我建议为 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,您可以在其中提取值并对其应用控制流以构建一个逻辑。