0

我从两个端点接收到相同的 json 结构,唯一不同的是 json 中的键。在响应#1我得到

[
    {
        "id": 45,
        "chapter__book__name": "Alonso",
        "chapter__book__id": 70,
        "chapter__chapter": 2,
        "verse": "",
        "verse_number": 5,
        "chapter": 97
    },
]

在响应#2我得到:

[
    {
        "id": 962,
        "book_name": "Title here",
        "book_id": 70,
        "chapter_number": 32,
        "verse": "xxx",
        "verse_number": 24,
        "chapter": 127
    },
]

一个结构可以同时解码这两者吗?目前我的结构看起来像这样:

struct Verse: Decodable, Identifiable {
    let id: Int
    let book_name: String
    let book_id: Int
    let verse: String
    let verse_number: Int
    let chapter: Int // chapter Id in database
    let chapter_number: Int
}

匹配响应#2,但不匹配响应#1。

4

1 回答 1

1

@lorem ipsum 的方法应该可以使用我自己没有尝试使用 swiftUI,但是处理 2 种不同类型的对象感觉有点复杂。尽管它们共享一个通用协议,但由于将被解码的是同一个对象,因此跟踪一个单一类型似乎很自然。

正如@Larme 所述,它可以使用自定义init(from decoder: Decoder)方法来完成。

import UIKit

let jsonA = """
[
    {
        "id": 45,
        "chapter__book__name": "Alonso",
        "chapter__book__id": 70,
        "chapter__chapter": 2,
        "verse": "",
        "verse_number": 5,
        "chapter": 97
    },
]
"""

let jsonB = """
[
    {
        "id": 962,
        "book_name": "Title here",
        "book_id": 70,
        "chapter_number": 32,
        "verse": "xxx",
        "verse_number": 24,
        "chapter": 127
    },
]
"""

protocol VerseCodingKey: CodingKey {
    static var id: Self { get }
    static var book_name: Self { get }
    static var book_id: Self { get }
    static var verse: Self { get }
    static var verse_number: Self { get }
    static var chapter: Self { get }
    static var chapter_number: Self { get }
}

struct Verse: Decodable {
    var id: Int
    var book_name: String
    var book_id: Int
    var verse: String
    var verse_number: Int
    var chapter: Int
    var chapter_number: Int
    
    enum CodingKeysA: String, VerseCodingKey {
        case id
        case book_name
        case book_id
        case verse
        case verse_number
        case chapter
        case chapter_number
    }
    
    enum CodingKeysB: String, VerseCodingKey {
        case id
        case book_name = "chapter__book__name"
        case book_id = "chapter__book__id"
        case verse
        case verse_number
        case chapter = "chapter__chapter"
        case chapter_number = "chapter"
    }
    
    init(from decoder: Decoder) throws {
        do {
            try self.init(from: decoder, verseCodingKey: CodingKeysA.self)
            return
        } catch { }
        
        do {
            try self.init(from: decoder, verseCodingKey: CodingKeysB.self)
            return
        } catch { }
        
        throw CustomError.unmatchedCodingKeys
    }
    
    init<T: VerseCodingKey>(from decoder: Decoder, verseCodingKey: T.Type) throws {
        do {
            let values = try decoder.container(keyedBy: T.self)
            id = try values.decode(Int.self, forKey: .id)
            book_name = try values.decode(String.self, forKey: .book_name)
            book_id = try values.decode(Int.self, forKey: .book_id)
            verse = try values.decode(String.self, forKey: .verse)
            verse_number = try values.decode(Int.self, forKey: .verse_number)
            chapter = try values.decode(Int.self, forKey: .chapter)
            chapter_number = try values.decode(Int.self, forKey: .chapter_number)
        } catch {
            throw CustomError.missingCodingKey
        }
    }
}

enum CustomError: Error {
    case missingCodingKey
    case unmatchedCodingKeys
 }

let dataA = jsonA.data(using: .utf8)!
let dataB = jsonB.data(using: .utf8)!
let verseA = try? JSONDecoder().decode([Verse].self, from: dataA)
let verseB = try? JSONDecoder().decode([Verse].self, from: dataB)

此代码适用于游乐场


旁注:

关键是要处理两个不同CodingKey的 s。

由于这种演变,现在使枚举符合协议是可行的,在深入探讨您的问题之前,我现在没有这样做。这使得代码更加直接和可重用。

可能有更好的方法来处理该do catch机制,但在这一点上是可以接受的。正如@Cristik 在评论中所说,您应该增强错误处理机制,因为您不想让所有错误都通过。见下面他的评论

这就是我在这个小实验中能走多远,我认为有人能做得更好。使用单个具体类而不是两个加一个协议似乎仍然更可靠,但同样,我不是假装是专家。

于 2021-09-30T21:52:16.767 回答