때로는 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
고객님께id
API가 반환되면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)
}
}
그런 다음 구조에서 이 새 유형을 사용할 수 있습니다.한 가지 사소한 단점은 구조체의 소비자가 랩된 문자열에 액세스하기 위해 다른 방향 지정을 해야 한다는 것입니다.단, 디코딩을 선언함으로써 이를 피할 수 있습니다.RelaxedString
private로 속성을 설정하고 퍼블릭인터페이스에 계산된 속성을 사용합니다.
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
}
}
위의 접근방식의 장점:
- 커스텀 쓸 필요 없음
init(from decoder: Decoder)
디코딩되는 속성 수가 증가하면 지루해질 수 있는 코드 - 재사용 가능성 -
RelaxedString
다른 구조에서도 심리스하게 사용할 수 있다 - id가 문자열 또는 int로부터 디코딩될 수 있다는 사실은 구현 세부사항으로 남습니다.
GeneralProduct
아이디 int int. - 퍼블릭 인터페이스는 문자열 값을 공개하여 여러 유형의 데이터를 처리할 필요가 없기 때문에 소비자 코드를 단순하게 유지합니다.
아래 유형을 처리할 수 있는 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
'programing' 카테고리의 다른 글
Retrofit을 사용한 동적 키 Json 문자열 해석 (0) | 2023.02.23 |
---|---|
Docker 컨테이너의 Apache, PHP, WordPress 캐시 문제 (0) | 2023.02.23 |
Ajax, 클릭 시 다중 요청 방지 (0) | 2023.02.23 |
React/Jsx에서 렌더 내의 함수를 호출하는 방법 (0) | 2023.02.23 |
모델 데이터에 따라 img src를 조건부로 변경 (0) | 2023.02.23 |