(快速)使用JSONDecoder解析JSON,JSONDecoder的对象会根据消息更改结构

时间:2019-02-14 22:59:07

标签: json swift decode

问题基本上可以归结为这一点。我的应用正在接收以下JSON格式的消息:

{
    "action": "ready",
    "data": null
}

{
    "action": "error",
    "data": {
        "code": String,
        "exception": String,
        "status": Int
    }
}

{
    "action": "messageRequest",
    "data": {
        "recipientUserId": String,
        "platform": String,
        "content": String
    }
}

{
    "action": "tabsChange",
    "data": {
        "roomsTabs": [{
            "configuration": {
                "accessToken": STRING,
                "url": STRING,
                "userId": STRING
            },
            "id": STRING,
            "isOnline": BOOLEAN,
            "isUnread": BOOLEAN,
            "lastActive": NUMBER,
            "name": STRING,
            "participantBanned": BOOLEAN,
            "platform": STRING,
            "secondParticipant": {
                "id": STRING,
                "platform": STRING,
                "userId": STRING
            },
            "secondParticipantId": STRING,
            "state": STRING,
            "unreadMessages": NUMBER
        ]}
    }
}

如您所见,数据对象具有不同的结构,具体取决于消息,并且消息对象可能变大(其中有10个以上)。 我不想手工分析所有内容,逐场分析,理想的解决方案当然是:

struct ChatJsCommand: Codable {
    let action: String
    let data: Any?
}
self.jsonDecoder.decode(ChatJsCommand.self, from: jsonData))

当然由于任何原因,这都不符合Codable。当然,我只能手动提取动作字段,创建动作映射(枚举)以构造结构类型,然后执行JSONDecoder().decode(self.commandMap[ActionKey], data: jsonData)。此解决方案可能还需要一些转换为适当的结构类型,才能在解析后使用这些对象。 但是也许有人有更好的方法?那班不是300行吗?任何想法都非常感谢。

1 个答案:

答案 0 :(得分:1)

让我们从定义数据协议开始,它可以是一个空协议。以后对我们有帮助:

protocol MessageData: Decodable {
}

现在让我们准备数据对象:

struct ErrorData: MessageData {
   let code: String
   let exception: String
   let status: Int
}

struct MessageRequestData: MessageData {
   let recipientUserId: String
   let platform: String
   let content: String
}

(在需要时使用可选选项)

现在,我们还必须知道数据类型:

enum ActionType: String {
   case ready
   case error
   case messageRequest
}

现在最困难的部分:

struct Response: Decodable {
   let action: ActionType
   let data: MessageData?

   private enum CodingKeys: String, CodingKey {
       case action
       case data
   }

   public init(from decoder: Decoder) throws {
      let values = try decoder.container(keyedBy: CodingKeys.self)
      let action = try values.decode(ActionType.self, forKey: .action)

      self.action = action

      switch action {
         case .ready:
             data = nil
         case .messageRequest:
             data = try values.decode(MessageRequestData.self, forKey: .data)
         case .error:
             data = try values.decode(ErrorData.self, forKey: .data)                 
      }
   }
}

唯一的技巧是首先解码action,然后根据内部值进行解析。唯一的问题是,在使用Response时,始终必须先检查action,然后将data强制转换为所需的类型。 可以通过将actiondata合并到一个与关联对象(例如对象)的枚举中来缓解这种情况。 :

enum Action {
   case ready
   case error(ErrorData)
   case messageRequest(MessageRequestData)
   case unknown
}

struct Response: Decodable {           
   let action: Action

   private enum CodingKeys: String, CodingKey {
      case action
      case data
   }

   public init(from decoder: Decoder) throws {
       let values = try decoder.container(keyedBy: CodingKeys.self)
       let actionString = try values.decode(String.self, forKey: .action)

       switch actionString {
          case "ready":
             action = .ready
          case "messageRequest":
             let data = try values.decode(MessageRequestData.self, forKey: .data)
             action = .messageRequest(data)
          case "error":
             let data = try values.decode(ErrorData.self, forKey: .data)
             action = .error(data)                 
          default: 
             action = .unknown             
       }
   }
}