【问题标题】:Parse JSON with different keys to same object using Codable in Swift在 Swift 中使用 Codable 解析具有不同键的 JSON
【发布时间】:2021-03-25 04:21:56
【问题描述】:

我从不同的 API 收到以下 2 个响应

{
  "id": "jdu72bdj",
  "userInfo": {
    "name": "Sudhanshu",
    "age": 28,
    "country": "India"
  }
}

{
  "profileId": "jdu72bdj",
  "profileDetails": {
    "name": "Sudhanshu",
    "age": 28,
    "country": "India"
  }
}

这是在 iOS development 使用 Swift 语言的上下文中。 基本上对象结构相同,但键不同。我正在使用Codable 解析这些,但我想不出一种使用相同结构进行解析的方法。我能想到的就是制作两个这样的不同结构-

public struct Container1: Codable {
  public let id: String
  public let userInfo: UserProfile?    
}

public struct Container2: Codable {
  public let profileId: String
  public let profileDetails: UserProfile?    
}

它们都使用通用的UserProfile 结构。

public struct UserProfile: Codable {
  public let name: String?
  public let age: Int?
  public let country: String?
}

有没有办法为两个响应使用一个通用容器结构来解析来自 2 个不同键的响应。我不想要 Container1 和 Container2,因为它们的结构相同。

有什么建议吗?

【问题讨论】:

    标签: ios json swift parsing codable


    【解决方案1】:

    一种解决方案是使用自定义密钥解码策略,使用 Apple 的 documentation 中的 CodingKey 实现。这个想法是将两个 json 消息的键映射到将用于两个消息的 struct Container 的属性。

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .custom({ keys in
        let key = keys.last!.stringValue
        switch key {
        case "id", "profileId":
            return AnyKey(stringValue: "id")!
        case "userInfo", "profileDetails":
            return AnyKey(stringValue: "details")!
        default:
            return keys.last!
        }        
    })
    

    CodingKey 的自定义实现在哪里

    struct AnyKey: CodingKey {
        var stringValue: String
        var intValue: Int?
    
        init?(stringValue: String) {
            print(stringValue)
            self.stringValue = stringValue
            intValue = nil
        }
    
        init?(intValue: Int) {
            self.stringValue = String(intValue)
            self.intValue = intValue
        }
    }
    

    然后使用下面的结构以相同的方式解码两个 json 消息

    struct Container: Codable {
        let id: String
        let details: UserProfile
    }
    
    let result = try decoder.decode(Container.self, from: data)
    

    【讨论】:

    • 你没有提到Container struct 应该是什么样子。显然 OP 只需要使用 iddetails 作为属性。
    • @gcharita 感谢您指出这一点。我会更新我的答案。
    • 可能需要添加您的keyDecodingStrategy 的解释,即将idprofileId 视为关键iduserInfoprofileDetails 作为details .因此,当它击中您的init(decoder:) 时,您将用您的“替换”“真正的钥匙”。我不知道,很高兴看到这个答案!
    • @Larme CodingKey 协议要求它同时处理 String 和 Int
    • @cheeseRoot 然后我猜你将不得不寻找另一种解决方案,除非你可以重写那个其他模块以便它支持依赖注入。无论哪种方式,这是原始问题中未提及的新要求。
    【解决方案2】:

    你可以从解码器中使用你自己的初始化

    struct UniversalProfileContainer: Decodable {
        struct UserProfile: Decodable {
            var name: String
            var age: Int
            var country: String
        }
        
        enum CodingKeys: String, CodingKey {
            case id = "id"
            case profileId = "profileId"
            case userInfo = "userInfo"
            case profileDetails = "profileDetails"
        }
    
        let id: String
        let profile: UserProfile
    
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            do {
                id = try container.decode(String.self, forKey: .id)
            } catch {
                id = try container.decode(String.self, forKey: .profileId)
            }
            do {
                profile = try container.decode(UserProfile.self, forKey: .userInfo)
            } catch {
                profile = try container.decode(UserProfile.self, forKey: .profileDetails)
            }
        }
    }
    
    let first = """
    {
      "id": "jdu72bdj",
      "userInfo": {
        "name": "Sudhanshu",
        "age": 28,
        "country": "India"
      }
    }
    """
    let second = """
    {
      "profileId": "jdu72bdj",
      "profileDetails": {
        "name": "Sudhanshu",
        "age": 28,
        "country": "India"
      }
    }
    """
    let response1 = try? JSONDecoder().decode(UniversalProfileContainer.self,
                                                from: first.data(using: .utf8)!)
    
    let response2 = try? JSONDecoder().decode(UniversalProfileContainer.self,
                                                from: second.data(using: .utf8)!)
    

    【讨论】:

      猜你喜欢
      • 2020-07-28
      • 1970-01-01
      • 1970-01-01
      • 2020-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-28
      相关资源
      最近更新 更多