【问题标题】:Flattening JSON when keys are known only at runtime当键仅在运行时已知时展平 JSON
【发布时间】:2018-01-21 19:53:35
【问题描述】:

假设我们有如下 JSON 结构(通常用于 Firebase 的实时数据库):

{
  "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对象的根字符串,例如: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 不适合这类情况吗?

【问题讨论】:

    标签: json swift4 codable decodable


    【解决方案1】:

    在我回答你的问题之前有几件事:

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

    2:BoringEntity中的id属性从何而来?

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

    如果我忽略了这些事情,您可以将BoringEntity 的数组包装在一个结构(BoringEntities)中。不建议直接使用[BoringEntity],因为您必须覆盖默认的init(from decoder:)Array

    这里的技巧是让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
    

    【讨论】:

    • 我后来加了注释来解释 id 属性的来源,明知这会使 json 无效
    • 所以id 是,例如。 18348b9b-9a49-4e04-ac35-37e38a8db1e2?
    • 确实如此。我将编辑我的问题以使其清楚,而不会使 json 样本无效
    • 我认为您已经有了一个很好的解决方案。唯一的缺点似乎是您不能将 entities 数组按照它在 JSON 中出现的顺序保留。
    • 你的 PhantomKeys 结构非常棒。那是我错过的一步
    【解决方案2】:

    基础实体:

    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:使用包装器

    感谢 Code Different,我能够将我的方法与他的 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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-01
      相关资源
      最近更新 更多