2017-11-27 2 views
1

Ich verwende die Codable Protokolle, um JSON von einer Web-API zu dekodieren. Mein Swift Datenmodell für diese API enthält Klassenvererbung (Unterklassen) und Komposition (Objekte als Eigenschaften anderer Objekte). In JSON kann derselbe Eigenschaftsname ein vollständiges Objekt oder eine einzelne Zeichenfolge darstellen, die die ID dieses Objekts in einer Datenbank angibt.decoder.container (keyedBy :) löst DecodingError.typeMismatch-Fehler aus. Codierbare Wanze?

meines Wissens das einzige Muster, diese Art von JSON für den Umgang mit Codable verwendet, ist die Decodierung zu tun „von Hand“ innerhalb des initializer des Objekts init(from decoder: Decoder) und zunächst versuchen, das gesamte Objekt zu entschlüsseln. Sollte dies fehlschlagen (indem ein Fehler geworfen wird, der abgefangen werden muss), dann versuchen Sie erneut, die gleiche Eigenschaft wie eine String zu dekodieren.

Dies funktioniert gut, solange das Objekt, das die Eigenschaft varient enthält, keine Unterklasse einer anderen Klasse Decodable ist. Wenn dies der Fall ist, löst das Dekodieren der Eigenschaften der Basisklasse den Fehler DecodingError.typeMismatch beim Aufruf der Funktion decoder.container(keyedBy:) aus.

Siehe meinen Beispielcode unten.

Ist das ein bekannter Fehler? Und/oder fehlt mir in dieser Situation eine alternative Methode der Entschlüsselung?

Im Übrigen wird derselbe Fehler innerhalb einer einzigen Funktion ausgelöst, wenn decoder.container(keyedBy:) aufgerufen wird, nachdem ein DecodingError.typeMismatch Fehler ausgelöst wurde, auch wenn dieser Fehler abgefangen wurde.

import Foundation 

// A `Base` class 
class Base: Codable { 
    var baseProperty: String? = "baseProperty" 
    init() {} 

    private enum CodingKeys: String, CodingKey { case baseProperty } 

    required init(from decoder: Decoder) throws { 
//===>> The next line will throw DecodingError.typeMismatch 
     let container = try decoder.container(keyedBy: CodingKeys.self) 
     baseProperty = try container.decode(String.self, forKey: .baseProperty) 
    } 
} 

// A Subclass of `Base` 
class Sub: Base { 
    // An `Other` class which is a property of the `Sub` class 
    class Other: Codable { var id: String? = "otherID" } 
    var subProperty: Other? = nil 
    override init() { super.init() } 

    private enum CodingKeys: String, CodingKey { case subProperty } 

    required init(from decoder: Decoder) throws { 
     let container = try decoder.container(keyedBy: CodingKeys.self) 
     do { subProperty = try container.decode(Other.self, 
               forKey: .subProperty) 
     } 
     catch { // We didn't find a whole `Other` object in the JSON; look for a `String` instead 
      let s = try container.decode(String.self, forKey: .subProperty) 
      subProperty = Other() 
      subProperty?.id = s 
     } 
     try super.init(from: decoder) 
    } 
} 

// Some sample JSON data: 
let json = """ 
{"baseProperty" : "baseProperty", 
"subProperty" : "someIDString"} 
""".data(using: .utf8)! 

// MAIN program ----------------------------------------------------- 
// Decode the JSON to produce a new Sub class instance 
do { 
    _ = try JSONDecoder().decode(Sub.self, from: json) 
} 
catch DecodingError.typeMismatch(_, let context) { 
    print("DecodingError.typeMismatch: \(context.debugDescription)") 
    print("DecodingError.Context: codingPath:") 
    for i in 0..<context.codingPath.count { print(" [\(i)] = \(context.codingPath[i])") } 
} 

Antwort

1

Diese is a known bug, die in this pull request behoben wurde (und wird es in Swift 4.1 machen).

Das Problem ist im Grunde, dass, wenn die Decodierung Other ausfällt, wird der Decoder vergessen seinen Behälter aus seinem internen Stapel abspringt, also was bedeutete, dass jeder künftigen Start in dem verschachtelten Behälter für .subProperty decodiert; Daher erhalten Sie einen Typenkonfliktfehler beim Versuch, ein Objekt von dort zu dekodieren (da es nur eine Zeichenfolge gibt!).

Bis behoben, ist eine Problemumgehung statt decode(_:forKey:); Holen Sie sich einen Super-Decoder, und versuchen Sie dann, eine Other davon zu entschlüsseln.

diese So ersetzen:

subProperty = try container.decode(Other.self, forKey: .subProperty) 

mit diesem:

let subPropertyDecoder = try container.superDecoder(forKey: .subProperty) 
subProperty = try Other(from: subPropertyDecoder) 

Dies funktioniert, weil wir jetzt eine völlig neue Decoder-Instanz haben, können wir nicht verderben den Stapel des Hauptdekodierers .

+0

OMG Ich schulde dir ein Bier. ;-) –

Verwandte Themen