2017-11-27 8 views
1

Codable 프로토콜을 사용하여 웹 API에서 JSON을 디코딩했습니다. 내 Swift이 API의 데이터 모델에는 클래스 상속 (하위 클래스)과 컴포지션 (다른 개체의 속성으로 사용되는 개체)이 모두 포함됩니다. JSON에서 동일한 속성 이름은 완전한 객체 또는 데이터베이스에서 해당 객체의 ID를 나타내는 단일 문자열을 나타낼 수 있습니다.decoder.container (keyedBy :)가 DecodingError.typeMismatch 오류를 발생시킵니다. 코드 가능 버그?

Codable을 사용하는 JSON을 처리하는 유일한 패턴은 객체의 초기화 자 init(from decoder: Decoder) 내에서 "수동으로"디코딩을 수행하고 먼저 전체 객체를 디코딩하려고하는 것입니다. 실패하면 (catch해야하는 오류를 던짐) 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의 중첩 된 용기에 시작를 디코딩 즉, 내부 스택에서 컨테이너를 팝업하는 것을 잊지 것입니다; 따라서 거기에서 객체를 디코딩하려고 시도 할 때 왜 유형 불일치 오류가 발생합니까?

고정 될 때까지 한 가지 해결 방법은 decode(_:forKey:)을 사용하는 것이 아니라; 수퍼 디코더을 가져온 다음 Other의 디코딩을 시도하십시오.

그래서 대체 :

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

를이와 함께 :

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

지금 우리는 완전히 새로운 디코더 인스턴스를 가지고 있기 때문에이 작동, 우리가 할 수있는 손상되지 주요 디코더의 스택을 .

+0

나는 당신에게 맥주를 빚지고있다. ;-) –