Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
271 views
in Technique[技术] by (71.8m points)

swift4 - Flattening JSON when keys are known only at runtime

Let's say we have a JSON structure like the following (commonly used in Firebase's Realtime Database):

{
  "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"
  }
}

Expected solution:

Using Decodable I'd like to convert it into an array of 3 elements:

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()...

The id attribute corresponds to the json object's root string , e.g: 18348b9b-9a49-4e04-ac35-37e38a8db1e2.

Workaround:

I have already tried several approaches but couldn't get the id attribute without requiring an auxiliary entity (or using optionals):

/// 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(...) }

Using init(from: Decoder) isn't as trivial as in other cases since keyedContainer(,) can't be used due to the fact that the key is unknown.


Is Decodable unsuited for these types of cases ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

A couple things before I answer your question:

1: The comment (// id) makes the JSON invalid. JSON does not allow comments.

2: Where does the id property in BoringEntity come from?

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

If I overlook these things, you can wrap the array of BoringEntity in a struct (BoringEntities). Using [BoringEntity] directly is not advisable since you have to overshadow the default init(from decoder:) of Array.

The trick here is to make JSONDecoder gives you back the list of keys via the container.allKeys property:

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

Usage:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...