嵌套关联值的自定义解码器初始化

问题描述 投票:0回答:1

我正在尝试为 Decodable 协议实现自定义

init(from decoder: Decoder) throws {}
,但出现错误:

DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Foundation.JSONValue>)
Expected to decode Dictionary<String, JSONValue> but found a string instead.
struct Model: Decodable {
  let title: String
  let gotoAction: GotoAction // String work fine
}

enum GotoAction: Decodable {
  case noAction
  case websiteLink(String)
  case sheet(OpenSheet)

  private enum CodingKeys: String, CodingKey {
    case noAction
    case websiteLink
    case sheet
  }

   init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

       if let link = try container.decodeIfPresent(String.self, forKey: .websiteLink) {
        self = .websiteLink(link)
       }

       if let sheet = try container.decodeIfPresent(OpenSheet.self, forKey: .sheet) {
        self = .sheet(sheet)
       }

      //  let noAction = try container.decodeIfPresent(String.self, forKey: .noAction)
      //  self = noAction

      throw DecodingError.dataCorruptedError(forKey: .websiteLink, in: container, debugDescription: "No match")
    }
}

enum OpenSheet: Decodable {
  case firstBanner
  case secondBanner(String)
}

let json = """
{
  "title": "Hello",
  "goto_action": "no_action"
}
"""

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let result = try decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(result)

我需要做什么才能使代码正常工作?

json swift enums decode
1个回答
0
投票

我认为仅凭

JsonDecoder
无法完成此任务。本质上
gotoAction
的值是
String
类型,而不是有效的 Json。

这就是您收到的错误消息的含义。

您需要自己解释该字符串并将其转换为枚举。

我已经使用您为

GotoAction
枚举提供的信息实施了一个可能的解决方案。当然,这需要更多的工作,例如检查正确的密钥并适当地抛出错误。

struct Model: Decodable {
    let title: String
    let gotoAction: GotoAction // String work fine
    
    private enum CodingKeys: String, CodingKey{
        case title
        case gotoAction
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.title = try container.decode(String.self, forKey: .title)
        
        let enumString = try container.decode(String.self, forKey: .gotoAction)
        self.gotoAction = try GotoAction.fromString(enumString)
    }
    
}

enum GotoAction: Decodable {
    case noAction
    case websiteLink(String)
    case sheet(OpenSheet)
    
    static func fromString(_ enumString: String) throws -> GotoAction{
        
        let array = enumString.split(separator: "/")
        let key = array.first!
        let associatedValue = array.dropFirst().joined(separator: "/")

        if key == "website_link"{
            return .websiteLink(associatedValue)
        }
        else if key == "sheet"{
            return .sheet(try OpenSheet.fromString(associatedValue))
        }
        
        return .noAction
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.