programing

단일 요소 디코딩에 실패하면 Swift JSONDecode 디코딩 어레이가 실패함

minimums 2023. 3. 19. 18:02
반응형

단일 요소 디코딩에 실패하면 Swift JSONDecode 디코딩 어레이가 실패함

다음과 문제가 했습니다. Swift4의 Codable을 허용하는 것 .JSONDecoder배열의 요소를 건너뜁니다.JSON을 있습니다.

[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]

코드 가능한 구조:

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

이 json을 디코딩할 때

let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)

: " " "products어어있있있있다다의 두 에 JSON이 때문에 "points"와 동시에, 「」, 「」points는 생략할 수 없습니다.GroceryProduct★★★★★★★★★★★★★★★★★★.

는 어떻게 '우리에게'를 허용할 수 입니다.JSONDecoder★★★★★★★★★★★★★★★★★★★★★★★★?

은 특정 입니다.이러한 타입은, 「」를 격납하는 것입니다.저장하는 경우nil「 」:

struct FailableDecodable<Base : Decodable> : Decodable {

    let base: Base?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.base = try? container.decode(Base.self)
    }
}

'다보다'를 '다보다'로 수 .GroceryProductBase다음 중 하나:

import Foundation

let json = """
[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]
""".data(using: .utf8)!


struct GroceryProduct : Codable {
    var name: String
    var points: Int
    var description: String?
}

let products = try JSONDecoder()
    .decode([FailableDecodable<GroceryProduct>].self, from: json)
    .compactMap { $0.base } // .flatMap in Swift 4.0

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]

그러면이렇게사용을합니다..compactMap { $0.base } 내다nil요소(디코딩 시 오류를 발생시킨 요소).

그러면 중간 배열이 생성됩니다.[FailableDecodable<GroceryProduct>]이것은 문제가 되지 않습니다.그러나 회피하고 싶은 경우는, 항상 다른 래퍼 타입을 작성해, 각 요소를 디코딩 해 키 없는 컨테이너로부터 언랩 할 수 있습니다.

struct FailableCodableArray<Element : Codable> : Codable {

    var elements: [Element]

    init(from decoder: Decoder) throws {

        var container = try decoder.unkeyedContainer()

        var elements = [Element]()
        if let count = container.count {
            elements.reserveCapacity(count)
        }

        while !container.isAtEnd {
            if let element = try container
                .decode(FailableDecodable<Element>.self).base {

                elements.append(element)
            }
        }

        self.elements = elements
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(elements)
    }
}

그런 다음 다음과 같이 디코딩합니다.

let products = try JSONDecoder()
    .decode(FailableCodableArray<GroceryProduct>.self, from: json)
    .elements

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]

는 새로운 .Throwable에 준거한 모든 타입을 랩할 수 있습니다.Decodable:

enum Throwable<T: Decodable>: Decodable {
    case success(T)
    case failure(Error)

    init(from decoder: Decoder) throws {
        do {
            let decoded = try T(from: decoder)
            self = .success(decoded)
        } catch let error {
            self = .failure(error)
        }
    }
}

「」의 하기 .GroceryProduct)Collection

let decoder = JSONDecoder()
let throwables = try decoder.decode([Throwable<GroceryProduct>].self, from: json)
let products = throwables.compactMap { $0.value }

서 ''는value는 의 입니다.Throwable:

extension Throwable {
    var value: T? {
        switch self {
        case .failure(_):
            return nil
        case .success(let value):
            return value
        }
    }
}

이 옵션을 선택하겠습니다.enum 타입')Struct에러와 그 인덱스를 추적하는 것이 도움이 될 수 있기 때문입니다.

스위프트 5

Swift 5의 경우 enum를 들어 예를 들어.

struct Throwable<T: Decodable>: Decodable {
    let result: Result<T, Error>

    init(from decoder: Decoder) throws {
        result = Result(catching: { try T(from: decoder) })
    }
}

디코딩된 값의 래핑을 해제하려면result★★★★

let products = throwables.compactMap { try? $0.result.get() }

문제는 컨테이너를 통해 반복할 때 컨테이너.전류가 발생한다는 것입니다.인덱스가 증가하지 않으므로 다른 유형으로 다시 디코딩할 수 있습니다.

왜냐하면 전류는인덱스는 읽기 전용입니다. 해결 방법은 더미를 성공적으로 디코딩하는 것입니다.@Hamish 솔루션을 사용하여 커스텀 init으로 래퍼를 작성했습니다.

이 문제는 현재 Swift 오류입니다.https://bugs.swift.org/browse/SR-5953

여기에 게재된 솔루션은 코멘트 중 하나의 회피책입니다.네트워크 클라이언트에서 여러 모델을 동일한 방식으로 구문 분석하고 있으며 솔루션이 개체 중 하나에 대해 로컬이길 원하기 때문에 이 옵션이 마음에 듭니다.즉, 나는 여전히 다른 것들은 버리기를 원한다.

내 github https://github.com/phynet/Lossy-array-decode-swift4에서 더 잘 설명할 수 있다.

import Foundation

    let json = """
    [
        {
            "name": "Banana",
            "points": 200,
            "description": "A banana grown in Ecuador."
        },
        {
            "name": "Orange"
        }
    ]
    """.data(using: .utf8)!

    private struct DummyCodable: Codable {}

    struct Groceries: Codable 
    {
        var groceries: [GroceryProduct]

        init(from decoder: Decoder) throws {
            var groceries = [GroceryProduct]()
            var container = try decoder.unkeyedContainer()
            while !container.isAtEnd {
                if let route = try? container.decode(GroceryProduct.self) {
                    groceries.append(route)
                } else {
                    _ = try? container.decode(DummyCodable.self) // <-- TRICK
                }
            }
            self.groceries = groceries
        }
    }

    struct GroceryProduct: Codable {
        var name: String
        var points: Int
        var description: String?
    }

    let products = try JSONDecoder().decode(Groceries.self, from: json)

    print(products)

두 가지 옵션이 있습니다.

  1. 키가 누락될 수 있는 구조의 모든 멤버를 옵션으로 선언합니다.

    struct GroceryProduct: Codable {
        var name: String
        var points : Int?
        var description: String?
    }
    
  2. 합니다.nildiscloss.discloss.case.

    struct GroceryProduct: Codable {
        var name: String
        var points : Int
        var description: String
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            name = try values.decode(String.self, forKey: .name)
            points = try values.decodeIfPresent(Int.self, forKey: .points) ?? 0
            description = try values.decodeIfPresent(String.self, forKey: .description) ?? ""
        }
    }
    

속성 래퍼를 사용하여 Swift 5.1에서 가능한 솔루션:

@propertyWrapper
struct IgnoreFailure<Value: Decodable>: Decodable {
    var wrappedValue: [Value] = []

    private struct _None: Decodable {}

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        while !container.isAtEnd {
            if let decoded = try? container.decode(Value.self) {
                wrappedValue.append(decoded)
            }
            else {
                // item is silently ignored.
                try? container.decode(_None.self)
            }
        }
    }
}

다음으로 사용방법:

let json = """
{
    "products": [
        {
            "name": "Banana",
            "points": 200,
            "description": "A banana grown in Ecuador."
        },
        {
            "name": "Orange"
        }
    ]
}
""".data(using: .utf8)!

struct GroceryProduct: Decodable {
    var name: String
    var points: Int
    var description: String?
}

struct ProductResponse: Decodable {
    @IgnoreFailure
    var products: [GroceryProduct]
}


let response = try! JSONDecoder().decode(ProductResponse.self, from: json)
print(response.products) // Only contains banana.

주의: 속성 래퍼 기능은 응답을 구조체(최상위 배열이 아님)로 래핑할 수 있는 경우에만 작동합니다.이 경우에도 수동으로 래핑할 수 있습니다(가독성을 높이기 위해 typealias를 사용합니다).

typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>

let response = try! JSONDecoder().decode(ArrayIgnoringFailure<GroceryProduct>.self, from: json)
print(response.wrappedValue) // Only contains banana.

@sophy-swicz 솔루션을 사용하기 쉬운 확장에 약간의 수정을 가했습니다.

fileprivate struct DummyCodable: Codable {}

extension UnkeyedDecodingContainer {

    public mutating func decodeArray<T>(_ type: T.Type) throws -> [T] where T : Decodable {

        var array = [T]()
        while !self.isAtEnd {
            do {
                let item = try self.decode(T.self)
                array.append(item)
            } catch let error {
                print("error: \(error)")

                // hack to increment currentIndex
                _ = try self.decode(DummyCodable.self)
            }
        }
        return array
    }
}
extension KeyedDecodingContainerProtocol {
    public func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
        var unkeyedContainer = try self.nestedUnkeyedContainer(forKey: key)
        return try unkeyedContainer.decodeArray(type)
    }
}

그냥 이렇게 부르면 돼

init(from decoder: Decoder) throws {

    let container = try decoder.container(keyedBy: CodingKeys.self)

    self.items = try container.decodeArray(ItemType.self, forKey: . items)
}

위의 예에서는 다음과 같습니다.

let json = """
[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]
""".data(using: .utf8)!

struct Groceries: Codable 
{
    var groceries: [GroceryProduct]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        groceries = try container.decodeArray(GroceryProduct.self)
    }
}

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)

대신 다음과 같이 할 수도 있습니다.

struct GroceryProduct: Decodable {
    var name: String
    var points: Int
    var description: String?
}'

그리고 나서 그것을 얻는 동안:

'let groceryList = try JSONDecoder().decode(Array<GroceryProduct>.self, from: responseData)'

도 Swift 4에는 Swift 4 API에 가 발생할 수 있는 init(from: Decoder).

커스텀 디코딩을 실장하고 있는 솔루션은 1개뿐입니다.옵션 필드에 디폴트값을 부여하고 필요한 것은 다음과 같습니다.

struct GroceryProduct: Codable {
    let name: String
    let points: Int?
    let description: String

    private enum CodingKeys: String, CodingKey {
        case name, points, description
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        points = try? container.decode(Int.self, forKey: .points)
        description = (try? container.decode(String.self, forKey: .description)) ?? "No description"
    }
}

// for test
let dict = [["name": "Banana", "points": 100], ["name": "Nut", "description": "Woof"]]
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
    let decoder = JSONDecoder()
    let result = try? decoder.decode([GroceryProduct].self, from: data)
    print("rawResult: \(result)")

    let clearedResult = result?.filter { $0.points != nil }
    print("clearedResult: \(clearedResult)")
}

@Hamish는 모든 어레이에 대해 다음과 같이 동작하도록 개선했습니다.

private struct OptionalContainer<Base: Codable>: Codable {
    let base: Base?
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        base = try? container.decode(Base.self)
    }
}

private struct OptionalArray<Base: Codable>: Codable {
    let result: [Base]
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let tmp = try container.decode([OptionalContainer<Base>].self)
        result = tmp.compactMap { $0.base }
    }
}

extension Array where Element: Codable {
    init(from decoder: Decoder) throws {
        let optionalArray = try OptionalArray<Element>(from: decoder)
        self = optionalArray.result
    }
}

스위프트 5

이전 답변에서 영감을 받아 결과 열거 확장자 내에서 디코딩합니다.

당신은 그것에 대해 어떻게 생각하세요?


extension Result: Decodable where Success: Decodable, Failure == DecodingError {

    public init(from decoder: Decoder) throws {

        let container: SingleValueDecodingContainer = try decoder.singleValueContainer()

        do {

            self = .success(try container.decode(Success.self))

        } catch {

            if let decodingError = error as? DecodingError {
                self = .failure(decodingError)
            } else {
                self = .failure(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: error.localizedDescription)))
            }
        }
    }
    
}


사용.


let listResult = try? JSONDecoder().decode([Result<SomeObject, DecodingError>].self, from: ##YOUR DATA##)

let list: [SomeObject] = listResult.compactMap {try? $0.get()}


@해미쉬의 대답은 훌륭하다. ,, 이는 것은 수 .FailableCodableArray 삭제:

struct FailableCodableArray<Element : Codable> : Codable {

    var elements: [Element]

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let elements = try container.decode([FailableDecodable<Element>].self)
        self.elements = elements.compactMap { $0.wrapped }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(elements)
    }
}

최근에 비슷한 문제가 있었는데 조금 달랐어요.

struct Person: Codable {
    var name: String
    var age: Int
    var description: String?
    var friendnamesArray:[String]?
}

의 중 friendnamesArray이치노

케이스를 입니다.[String] an optional strings(옵션 문자열 )[String?]

struct Person: Codable {
    var name: String
    var age: Int
    var description: String?
    var friendnamesArray:[String?]?
}

설명을 옵션으로 설정했습니다.또, 다음과 같이, 0이 될 가능성이 있는 경우는, 포인트 필드를 옵션으로 할 필요가 있습니다.

struct GroceryProduct: Codable {
    var name: String
    var points: Int?
    var description: String?
}

사용하기에 적합하다고 생각되는 대로 안전하게 포장을 풀기만 하면 됩니다.실제 사용 사례에서는 0점 == 0으로 추정되므로 다음과 같은 예를 들 수 있습니다.

let products = try JSONDecoder().decode([GroceryProduct].self, from: json)
for product in products {
    let name = product.name
    let points = product.points ?? 0
    let description = product.description ?? ""
    ProductView(name, points, description)
}

또는 인라인:

let products = try JSONDecoder().decode([GroceryProduct].self, from: json)
for product in products {
    ProductView(product.name, product.points ?? 0, product.description ?? "")
}

가 생각해낸 건 이 ★★★★★★★★★★★★★★★★★★★★★★★★★.KeyedDecodingContainer.safelyDecodeArray다음과 같은 간단한 인터페이스를 제공합니다.

extension KeyedDecodingContainer {

/// The sole purpose of this `EmptyDecodable` is allowing decoder to skip an element that cannot be decoded.
private struct EmptyDecodable: Decodable {}

/// Return successfully decoded elements even if some of the element fails to decode.
func safelyDecodeArray<T: Decodable>(of type: T.Type, forKey key: KeyedDecodingContainer.Key) -> [T] {
    guard var container = try? nestedUnkeyedContainer(forKey: key) else {
        return []
    }
    var elements = [T]()
    elements.reserveCapacity(container.count ?? 0)
    while !container.isAtEnd {
        /*
         Note:
         When decoding an element fails, the decoder does not move on the next element upon failure, so that we can retry the same element again
         by other means. However, this behavior potentially keeps `while !container.isAtEnd` looping forever, and Apple does not offer a `.skipFailable`
         decoder option yet. As a result, `catch` needs to manually skip the failed element by decoding it into an `EmptyDecodable` that always succeed.
         See the Swift ticket https://bugs.swift.org/browse/SR-5953.
         */
        do {
            elements.append(try container.decode(T.self))
        } catch {
            if let decodingError = error as? DecodingError {
                Logger.error("\(#function): skipping one element: \(decodingError)")
            } else {
                Logger.error("\(#function): skipping one element: \(error)")
            }
            _ = try? container.decode(EmptyDecodable.self) // skip the current element by decoding it into an empty `Decodable`
        }
    }
    return elements
}
}

인 루프 " " "while !container.isAtEnd 고민이다를 합니다.또한 이 문제는EmptyDecodable.

훨씬 더 간단한 시도:포인트를 옵션으로 선언하거나 어레이에 옵션 요소를 포함시키는 것은 어떨까요?

let products = [GroceryProduct?]

특징:

  • 디코딩가능한 인스턴스의 1행:let array: CompactDecodableArray<Int>
  • 표준 매핑 메커니즘으로 디코딩됩니다.JSONDecoder().decode(Model.self, from: data)
  • 잘못된 요소를 건너뜁니다(정상적으로 매핑된 요소만 있는 배열 제외).

세부 사항

  • X코드 12.1 (12A7403)
  • 스위프트 5.3

솔루션

class CompactDecodableArray<Element>: Decodable where Element: Decodable {
    private(set) var elements = [Element]()
    required init(from decoder: Decoder) throws {
        guard var unkeyedContainer = try? decoder.unkeyedContainer() else { return }
        while !unkeyedContainer.isAtEnd {
            if let value = try? unkeyedContainer.decode(Element.self) {
                elements.append(value)
            } else {
                unkeyedContainer.skip()
            }
        }
    }
}

// https://forums.swift.org/t/pitch-unkeyeddecodingcontainer-movenext-to-skip-items-in-deserialization/22151/17

struct Empty: Decodable { }

extension UnkeyedDecodingContainer {
    mutating func skip() { _ = try? decode(Empty.self) }
}

사용.

struct Model2: Decodable {
    let num: Int
    let str: String
}

struct Model: Decodable {
    let num: Int
    let str: String
    let array1: CompactDecodableArray<Int>
    let array2: CompactDecodableArray<Int>?
    let array4: CompactDecodableArray<Model2>
}

let dictionary: [String : Any] = ["num": 1, "str": "blablabla",
                                  "array1": [1,2,3],
                                  "array3": [1,nil,3],
                                  "array4": [["num": 1, "str": "a"], ["num": 2]]
]

let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.array1.elements)")
print("2. \(object.array2?.elements)")
print("3. \(object.array4.elements)")

콘솔

1. [1, 2, 3]
2. nil
3. [__lldb_expr_25.Model2(num: 1, str: "a")]

언급URL : https://stackoverflow.com/questions/46344963/swift-jsondecode-decoding-arrays-fails-if-single-element-decoding-fails

반응형