扁平化JSON当密钥,只知道在运行时

问题描述:

比方说,我们有一个JSON结构如下内容(在火力地堡的实时数据库常用):扁平化JSON当密钥,只知道在运行时

{ 
    "18348b9b-9a49-4e04-ac35-37e38a8db1e2": { 
    "isActive": false, 
    "age": 29, 
    "company": "BALOOBA" 
    }, 
    "20aca96e-663a-493c-8e9b-cb7b8272f817": { 
    "isActive": false, 
    "age": 39, 
    "company": "QUONATA" 
    }, 
    "bd0c389b-2736-481a-9cf0-170600d36b6d": { 
    "isActive": false, 
    "age": 35, 
    "company": "EARTHMARK" 
    } 
} 

预期的解决方案:

使用Decodable我会喜欢将其转换成3个元素的数组:

struct BoringEntity: Decodable { 
    let id: String 
    let isActive: Bool 
    let age: Int 
    let company: String 

    init(from decoder: Decoder) throws { 
     // ... 
    } 
} 

let entities: [BoringEntity] = try! JSONDecoder()... 

ID属性对应于JSON对象的RO字符串,例如:18348b9b-9a49-4e04-ac35-37e38a8db1e2

解决方法:

我已经尝试过多种方法,但无需辅助实体(或者使用选配)不能得到id属性:

/// Incomplete BoringEntity version to make Decodable conformance possible. 
struct BoringEntityIncomplete: Decodable { 
    let isActive: Bool 
    let age: Int 
    let company: String 
} 

// Decode to aux struct 
let decoded = try! JSONDecoder().decode([String : BoringEntityIncomplete].self, for: jsonData) 
// Map aux entities to BoringEntity 
let entities = decoded.map { BoringEntity(...) } 

使用init(from: Decoder)并不像小事在其他情况下,由于密钥未知,因此不能使用keyedContainer(,)


Decodable不适合这些类型的案件?前

基础机构:

struct BoringEntity: Decodable { 
    let id: String 
    let isActive: Bool 
    let age: Int 
    let company: String 
} 

解决方法1:使用一个额外的结构没有密钥

/// Incomplete BoringEntity version to make Decodable conformance possible. 
private struct BoringEntityBare: Decodable { 
    let isActive: Bool 
    let age: Int 
    let company: String 
} 

// Decode to aux struct 
private let decoded = try! JSONDecoder().decode([String : BoringEntityBare].self, from: jsonData) 
// Map aux entities to BoringEntity 
let entities = decoded.map { BoringEntity(id: $0.key, isActive: $0.value.isActive, age: $0.value.age, company: $0.value.company) } 
print(entities) 

解决方案2:使用包装

由于代码的不同我能我的方法与他PhantomKeys想法结合起来,但没有绕过它:必须始终使用一个额外的实体。

struct BoringEntities: Decodable { 
    var entities = [BoringEntity]() 

    // This really is just a stand-in to make the compiler happy. 
    // It doesn't actually do anything. 
    private struct PhantomKeys: CodingKey { 
     var intValue: Int? 
     var stringValue: String 
     init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" } 
     init?(stringValue: String) { self.stringValue = stringValue } 
    } 

    private enum BareKeys: String, CodingKey { 
     case isActive, age, company 
    } 

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

     // There's only one key 
     for key in container.allKeys { 
      let aux = try container.nestedContainer(keyedBy: BareKeys.self, forKey: key) 

      let age = try aux.decode(Int.self, forKey: .age) 
      let company = try aux.decode(String.self, forKey: .company) 
      let isActive = try aux.decode(Bool.self, forKey: .isActive) 

      let entity = BoringEntity(id: key.stringValue, isActive: isActive, age: age, company: company) 
      entities.append(entity) 
     } 
    } 
} 

let entities = try JSONDecoder().decode(BoringEntities.self, from: jsonData).entities 
print(entities) 

一对夫妇的事情,我回答你的问题:

1:注释(// id)使JSON无效。 JSON不允许评论。

2:idBoringEntity属性来自哪里?

struct BoringEntity: Decodable { 
    let id: String   // where is it stored in the JSON??? 
    let isActive: Bool 
    let age: Int 
    let company: String 
} 

如果我忽略了这些东西,你可以在一个结构(BoringEntities)包裹的BoringEntity阵列。直接使用[BoringEntity]并不可取,因为您必须掩盖Array的默认init(from decoder:)

这里的技巧是让JSONDecoder通过container.allKeys财产给你回的密钥列表:

struct BoringEntity: Decodable { 
    let isActive: Bool 
    let age: Int 
    let company: String 
} 

struct BoringEntities: Decodable { 
    var entities = [BoringEntity]() 

    // This really is just a stand-in to make the compiler happy. 
    // It doesn't actually do anything. 
    private struct PhantomKeys: CodingKey { 
     var intValue: Int? 
     var stringValue: String 
     init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" } 
     init?(stringValue: String) { self.stringValue = stringValue } 
    } 

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

     for key in container.allKeys { 
      let entity = try container.decode(BoringEntity.self, forKey: key) 
      entities.append(entity) 
     } 
    } 
} 

用法:

let jsonData = """ 
{ 
    "18348b9b-9a49-4e04-ac35-37e38a8db1e2": { 
    "isActive": false, 
    "age": 29, 
    "company": "BALOOBA" 
    }, 
    "20aca96e-663a-493c-8e9b-cb7b8272f817": { 
    "isActive": false, 
    "age": 39, 
    "company": "QUONATA" 
    }, 
    "bd0c389b-2736-481a-9cf0-170600d36b6d": { 
    "isActive": false, 
    "age": 35, 
    "company": "EARTHMARK" 
    } 
} 
""".data(using: .utf8)! 

let entities = try JSONDecoder().decode(BoringEntities.self, from: jsonData).entities 
+0

我后来添加的注释来解释其中的id属性是从哪来的,明知它将使JSON无效 – nathan

+0

所以'id'是,例如。 '18348b9b-9a49-4e04-ac35-37e38a8db1e2'? –

+0

确实。我会编辑我的问题,以清除而不使json示例失效 – nathan