2017-12-07 2 views
1

Ich verwende das Decodable Protokoll, um JSON zu analysieren, das von einer externen Quelle empfangen wird. Nach dem Decodieren der Attribute, von denen ich weiß, dass sie da sind, können noch einige Attribute in dem JSON existieren, die unbekannt sind und noch nicht dekodiert wurden. Wenn beispielsweise die externe Quelle zu einem späteren Zeitpunkt ein neues Attribut zu dem JSON hinzugefügt hat, möchte ich diese unbekannten Attribute beibehalten, indem ich sie in einem [String: Any] Wörterbuch (oder einer Alternative) ablege, damit die Werte nicht ignoriert werden.Wie erhalten Sie die nichtdecodierten Attribute von einem Decoder-Container in Swift 4?

Das Problem ist, dass nach dem Decodieren der Attribute, die ich weiß, gibt es keine Zugriffsmethoden auf den Container, um die Attribute, die noch nicht dekodiert wurden abrufen. Ich kenne die decoder.unkeyedContainer(), die ich verwenden könnte, um über jeden Wert zu iterieren, aber dies würde in meinem Fall nicht funktionieren, denn damit das funktioniert, müssen Sie wissen, welchen Werttyp Sie durchlaufen, aber die Werttypen in JSON sind nicht immer identisch. Hier

ist ein Beispiel in Spielplatz für das, was ich versuche zu erreichen:

// Playground 
import Foundation 

let jsonData = """ 
{ 
    "name": "Foo", 
    "age": 21 
} 
""".data(using: .utf8)! 

struct Person: Decodable { 
    enum CodingKeys: CodingKey { 
     case name 
    } 

    let name: String 
    let unknownAttributes: [String: Any] 

    init(from decoder: Decoder) throws { 
     let container = try decoder.container(keyedBy: CodingKeys.self) 
     self.name = try container.decode(String.self, forKey: .name) 

     // I would like to store the `age` attribute in this dictionary 
     // but it would not be known at the time this code was written. 
     self.unknownAttributes = [:] 
    } 
} 

let decoder = JSONDecoder() 
let person = try! decoder.decode(Person.self, from: jsonData) 

// The `person.unknownAttributes` dictionary should 
// contain the "age" attribute with a value of 21. 

Ich mag würde für das unknownAttributes Wörterbuch das age Attribut und Wert in diesem Fall zu speichern, und anderen möglichen Wert Typen, wenn sie in Zukunft von der externen Quelle zum JSON hinzugefügt werden.

Der Grund, warum ich so etwas tun möchte, ist, dass ich die unbekannten Attribute im JSON beibehalten kann, so dass ich in einer zukünftigen Aktualisierung des Codes in der Lage sein werde, sie angemessen zu behandeln, sobald die Attributschlüssel bekannt sind .

Ich habe viel über StackOverflow und Google gesucht, bin aber noch nicht auf diesen einzigartigen Fall gestoßen. Danke im Voraus!

Antwort

1

Ihre Jungs mit neuen Möglichkeiten kommen immer wieder die Swift 4 Codierung APIs ... zu betonen;)

eine allgemeine Lösung, die Unterstützung alle Werttypen vielleicht nicht möglich sein. Aber für primitive Typen, können Sie dies versuchen:

Erstellen Sie eine einfache CodingKey Typ mit String-basierten Schlüssel:

struct UnknownCodingKey: CodingKey { 
    init?(stringValue: String) { self.stringValue = stringValue } 
    let stringValue: String 

    init?(intValue: Int) { return nil } 
    var intValue: Int? { return nil } 
} 

Dann schreiben Sie eine allgemeine Funktion Decodierung der Standard KeyedDecodingContainer durch die UnknownCodingKey oben getastet mit:

func decodeUnknownKeys(from decoder: Decoder, with knownKeys: Set<String>) throws -> [String: Any] { 
    let container = try decoder.container(keyedBy: UnknownCodingKey.self) 
    var unknownKeyValues = [String: Any]() 

    for key in container.allKeys { 
     guard !knownKeys.contains(key.stringValue) else { continue } 

     func decodeUnknownValue<T: Decodable>(_ type: T.Type) -> Bool { 
      guard let value = try? container.decode(type, forKey: key) else { 
       return false 
      } 
      unknownKeyValues[key.stringValue] = value 
      return true 
     } 
     if decodeUnknownValue(String.self) { continue } 
     if decodeUnknownValue(Int.self) { continue } 
     if decodeUnknownValue(Double.self) { continue } 
     // ... 
    } 
    return unknownKeyValues 
} 

Schließlich verwenden die decodeUnknownKeys Funktion Ihre unknownAttributes Wörterbuch füllen:

struct Person: Decodable { 
    enum CodingKeys: CodingKey { 
     case name 
    } 

    let name: String 
    let unknownAttributes: [String: Any] 

    init(from decoder: Decoder) throws { 
     let container = try decoder.container(keyedBy: CodingKeys.self) 
     self.name = try container.decode(String.self, forKey: .name) 

     let knownKeys = Set(container.allKeys.map { $0.stringValue }) 
     self.unknownAttributes = try decodeUnknownKeys(from: decoder, with: knownKeys) 
    } 
} 

Ein einfacher Test:

let jsonData = """ 
{ 
    "name": "Foo", 
    "age": 21, 
    "token": "ABC", 
    "rate": 1.234 
} 
""".data(using: .utf8)! 

let decoder = JSONDecoder() 
let person = try! decoder.decode(Person.self, from: jsonData) 
print(person.name) 
print(person.unknownAttributes) 

druckt:

Foo
[ "Alter": 21 "Token": "ABC", "Rate": 1.234]

Verwandte Themen