programing

때로는 Int 값, 때로는 String 값인 코드화 가능 사용

elseif 2023. 2. 23. 22:05

때로는 Int 값, 때로는 String 값인 코드화 가능 사용

특정 키 값을 반환하는 API가 있습니다(이 경우).id)는 Int로서 JSON에 포함되며, 그 이외의 경우 String과 같은 키 값을 반환합니다.그 JSON을 해석하려면 어떻게 코드화 가능합니까?

struct GeneralProduct: Codable {
    var price: Double!
    var id: String?
    var name: String!

    private enum CodingKeys: String, CodingKey {
        case price = "p"
        case id = "i"
        case name = "n"
    }

    init(price: Double? = nil, id: String? = nil, name: String? = nil) {
        self.price = price
        self.id = id
        self.name = name
    }
}

다음 오류 메시지가 계속 표시됩니다.Expected to decode String but found a number instead. 번호가 반환되는 이유는 id 필드가 비어 있고 id 필드가 비어 있으면 디폴트로0 이 반환되는 ID 로서 디폴트로 코드화 가능한 ID 로서 번호로서 반환되는 ID는 숫자로 식별됩니다.기본적으로는 ID 키를 무시할 수 있지만, 코드화할 수 있는 옵션으로는 무시할 수 없습니다.어떻게 대처하면 좋을까요?

여기 JSON이 있습니다.매우 간단합니다.

일해

{
  "p":2.12,
  "i":"3k3mkfnk3",
  "n":"Blue Shirt"
}

에러 - 시스템에 ID가 없기 때문에 디폴트로0 이 반환됩니다.이 디폴트에서는 코드화 가능은 스트링과 반대되는 수치로 간주됩니다.

{
  "p":2.19,
  "i":0,
  "n":"Black Shirt"
}
struct GeneralProduct: Codable {
    var price: Double?
    var id: String?
    var name: String?
    private enum CodingKeys: String, CodingKey {
        case price = "p", id = "i", name = "n"
    }
    init(price: Double? = nil, id: String? = nil, name: String? = nil) {
        self.price = price
        self.id = id
        self.name = name
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        price = try container.decode(Double.self, forKey: .price)
        name = try container.decode(String.self, forKey: .name)
        do {
            id = try String(container.decode(Int.self, forKey: .id))
        } catch DecodingError.typeMismatch {
            id = try container.decode(String.self, forKey: .id)
        }
    }
}

let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""

let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""

do {
    let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
    print(product.price ?? "nil")
    print(product.id ?? "nil")
    print(product.name ?? "nil")
} catch {
    print(error)
}

편집/갱신:

또, 간단하게 할당할 수도 있습니다.nil고객님께idAPI가 반환되면0:

do {
    let value = try container.decode(Int.self, forKey: .id)
    id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
    id = try container.decode(String.self, forKey: .id)
}

이것은, 다음의 방법으로 생각할 수 있습니다.MetadataType, 좋은 점은 그것이 일반적인 해결책이 될 수 있다는 것입니다.GeneralProduct단, 모든 것을 위해서struct같은 애매함을 가지고 있다:

struct GeneralProduct: Codable {
  var price:Double?
  var id:MetadataType?
  var name:String?

  private enum CodingKeys: String, CodingKey {
    case price = "p"
    case id = "i"
    case name = "n"
  }

  init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
    self.price = price
    self.id = id
    self.name = name
  }
}

enum MetadataType: Codable {
  case int(Int)
  case string(String)

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    do {
      self = try .int(container.decode(Int.self))
    } catch DecodingError.typeMismatch {
      do {
        self = try .string(container.decode(String.self))
      } catch DecodingError.typeMismatch {
        throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
      }
    }
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    switch self {
    case .int(let int):
      try container.encode(int)
    case .string(let string):
      try container.encode(string)
    }
  }
}

다음은 테스트입니다.

let decoder = JSONDecoder()
var json =  "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
  print(id) // 0
}

json =  "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
  print(id) // hello world
}

다음 중 하나에서 심리스하게 디코딩Int또는String코드를 작성해야 합니다.

단, 언어(속성 래퍼)에 (어느 정도)가 새롭게 추가되었기 때문에 필요에 따라 이 논리를 쉽게 재사용할 수 있습니다.

// note this is only `Decodable`
struct GeneralProduct: Decodable {
    var price: Double
    @Flexible var id: Int // note this is an Int
    var name: String
}

속성 래퍼 및 지원 코드는 다음과 같이 구현할 수 있습니다.

@propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
    var wrappedValue: T
    
    init(from decoder: Decoder) throws {
        wrappedValue = try T(container: decoder.singleValueContainer())
    }
}

protocol FlexibleDecodable {
    init(container: SingleValueDecodingContainer) throws
}

extension Int: FlexibleDecodable {
    init(container: SingleValueDecodingContainer) throws {
        if let int = try? container.decode(Int.self) {
            self = int
        } else if let string = try? container.decode(String.self), let int = Int(string) {
            self = int
        } else {
            throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
        }
    }
}

원답

기본 JSON 데이터 유형(문자열, 숫자, 부울)에서 디코딩하는 방법을 알고 있는 문자열 위에 래퍼를 사용할 수 있습니다.

struct RelaxedString: Codable {
    let value: String
    
    init(_ value: String) {
        self.value = value
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        // attempt to decode from all JSON primitives
        if let str = try? container.decode(String.self) {
            value = str
        } else if let int = try? container.decode(Int.self) {
            value = int.description
        } else if let double = try? container.decode(Double.self) {
            value = double.description
        } else if let bool = try? container.decode(Bool.self) {
            value = bool.description
        } else {
            throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(value)
    }
}

그런 다음 구조에서 이 새 유형을 사용할 수 있습니다.한 가지 사소한 단점은 구조체의 소비자가 랩된 문자열에 액세스하기 위해 다른 방향 지정을 해야 한다는 것입니다.단, 디코딩을 선언함으로써 이를 피할 수 있습니다.RelaxedStringprivate로 속성을 설정하고 퍼블릭인터페이스에 계산된 속성을 사용합니다.

struct GeneralProduct: Codable {
    var price: Double!
    var _id: RelaxedString?
    var name: String!
    
    var id: String? {
        get { _id?.value }
        set { _id = newValue.map(RelaxedString.init) }
    }

    private enum CodingKeys: String, CodingKey {
        case price = "p"
        case _id = "i"
        case name = "n"
    }

    init(price: Double? = nil, id: String? = nil, name: String? = nil) {
        self.price = price
        self._id = id.map(RelaxedString.init)
        self.name = name
    }
}

위의 접근방식의 장점:

  1. 커스텀 쓸 필요 없음init(from decoder: Decoder)디코딩되는 속성 수가 증가하면 지루해질 수 있는 코드
  2. 재사용 가능성 -RelaxedString다른 구조에서도 심리스하게 사용할 수 있다
  3. id가 문자열 또는 int로부터 디코딩될 수 있다는 사실은 구현 세부사항으로 남습니다.GeneralProduct아이디 int int.
  4. 퍼블릭 인터페이스는 문자열 값을 공개하여 여러 유형의 데이터를 처리할 필요가 없기 때문에 소비자 코드를 단순하게 유지합니다.

아래 유형을 처리할 수 있는 ValueWrapper 구조를 가진 Gist를 만들었습니다.

case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)

https://gist.github.com/amrangry/89097b86514b3477cae79dd28bba3f23

으로 @Cristik을 사용하여 다른 합니다.@propertyWrapper.

@propertyWrapper
struct StringForcible: Codable {
    
    var wrappedValue: String?
    
    enum CodingKeys: CodingKey {}
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let string = try? container.decode(String.self) {
            wrappedValue = string
        } else if let integer = try? container.decode(Int.self) {
            wrappedValue = "\(integer)"
        } else if let double = try? container.decode(Double.self) {
            wrappedValue = "\(double)"
        } else if container.decodeNil() {
            wrappedValue = nil
        }
        else {
            throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double."))
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
    
    init() {
        self.wrappedValue = nil
    }
    
}

그리고 용도는

struct SomeDTO: Codable {
   @StringForcible var id: String? 
}

- 내 생각엔.. - 내 생각엔..

struct AnotherDTO: Codable {
    var some: SomeDTO?
}

언급URL : https://stackoverflow.com/questions/47935705/using-codable-with-value-that-is-sometimes-an-int-and-other-times-a-string