2017-11-27 10 views
1

Codableプロトコルを使用して、Web APIからJSONをデコードしています。私のSwiftこのAPIのデータモデルには、クラスの継承(サブクラス)とコンポジション(他のオブジェクトのプロパティとしてのオブジェクト)の両方が含まれています。 JSONでは、同じプロパティ名が完全なオブジェクト、またはデータベース内のそのオブジェクトのIDを示す単一の文字列を表す場合があります。decoder.container(keyedBy :)はDecodingError.typeMismatchエラーをスローします。コーディング可能なバグ?

Codableを使用してこの種のJSONを処理する唯一のパターンは、オブジェクトの初期化子init(from decoder: Decoder)内で "手動で"デコードを行い、最初にオブジェクト全体のデコードを試みることです。それが失敗した場合(キャッチされなければならないエラーをスローすることによって)、Stringと同じプロパティのデコードを再試行します。

varientプロパティを含むオブジェクトが別のDecodableクラスのサブクラスでない限り、これはうまくいきます。その場合、基本クラスのプロパティをデコードすると、関数decoder.container(keyedBy:)の呼び出しにエラーDecodingError.typeMismatchがスローされます。

下記のサンプルコードをご覧ください。

これは既知のバグですか?そして/または私はこの状況で別のデコード方法を見逃していますか?

DecodingError.typeMismatchエラーがスローされた後にdecoder.container(keyedBy:)が呼び出された場合、そのエラーがキャッチされても、同じエラーが単一の関数内にスローされます。

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

答えて

1

this pull requestに固定されている(とスウィフト4.1にそれを行います)このis a known bug、。

問題は、基本的Otherの復号が失敗した場合、デコーダは、したがって将来は.subPropertyためのネストされたコンテナ内開始を復号することを意味し、その内部スタックからその容器をポップオフするために忘れてしまうということです。したがって、そこからオブジェクトをデコードしようとすると型不一致エラーが発生するのはなぜですか?

固定されるまで、回避策の1つはdecode(_:forKey:)を使用するのではなく、 スーパーデコーダを取得してから、Otherのデコードを試行してください。

ので、この置き換えます。これは、今、私たちは完全に新しいデコーダのインスタンスを持っているので、私たちはメインデコーダの壊れスタックすることはできません動作します

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

:これで

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

を。

+0

OMG私はあなたにビールを借りています。 ;-) –