用一个结构解码两个不同的 JSON 响应? [复制]

Posted

技术标签:

【中文标题】用一个结构解码两个不同的 JSON 响应? [复制]【英文标题】:Decoding two different JSON responses with one struct? [duplicate] 【发布时间】:2021-11-22 15:28:16 【问题描述】:

我从两个端点接收到相同的 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。

【问题讨论】:

可能有一个protocol,但有两个`struts。您需要 2 组合并类型的编码键 如何使用 2 个结构,每个用于每个端点,并有第三个是“您的应用程序如何管理它们”?否则,您可以使用自定义 init(from: decoder),但这是一些工作。 【参考方案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)

此代码适用于游乐场


附注:

关键是要兼顾两个不同的CodingKeys。

因为this evolution 现在可以使枚举符合协议,在深入研究您的问题之前我现在没有这样做。这使得代码更加直接和可重用。

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

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

【讨论】:

@Cristik 我没有得到你我必须说的包装部分。问题是,如果decode方法抛出,基本上意味着已经使用了错误的codingKeys来尝试解码json,因此当它失败时,必须使用另一组Keys调用该方法。如果没有 returns 语句,它总是会在最后抛出。我希望方法在解码成功时返回,如果我们到达 init 范围的末尾则抛出 @Cristik 我认为我们误会了。我的意思是,在这个init(from decoder: Decoder) 中,如果我去掉return 语句,它总是会到达不想要的throw CustomError.unmatchedCodingKeys。我希望它在init(from decoder:, verseCodingKey:) 没有抛出时结束,这意味着解码成功。我知道do catch 是如何工作的,我向你保证哈哈 @Cristik 完全同意您在第二条评论中提到的wrap 部分的说法。这是概念证明,而不是要复制/粘贴的东西。策略就在这里,现在必须根据他的需求和项目结构对其进行调整和增强(以及一些好的做法,例如不绕过所有可能引发的错误) @Cristik 不错!这是我把它扔掉的剩菜!谢谢

以上是关于用一个结构解码两个不同的 JSON 响应? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Swift:用未知键解码 JSON? [复制]

如何使用具有数组的 2 个结构解码 JSON

对 GMSCoordinateBounds 数组的 Swift 解码 JSON 响应

将 JSON 数据从 Parse Cloud Code 返回到 Swift 中的可解码结构

从具有特定结构的 json 解码数据时出现问题

有没有办法在不创建***结构的情况下将 JSON 解码为结构