如何使用动态类型的Swift JSONDecode?

我的应用程序具有本地缓存​​,并从/向服务器发送/接收模型.所以我决定构建一个map [String:Codable.Type],基本上能够解码我在本地创建或从服务器接收的通用缓存上的任何内容.

let encoder = JSONEncoder()
let decoder = JSONDecoder()
var modelNametoType = [String : Codable.Type]()
modelNametoType = ["ContactModel": ContactModel.Self,"AnythingModel" : AnythingModel.Self,...]

无论我在App上创建什么,我都可以成功编码并存储在缓存中,如下所示:

let contact = ContactModel(name: "John")
let data = try! encoder.encode(contact)
CRUD.shared.storekey(key: "ContactModel$10",contact)

我想像这样解码:

let result = try! decoder.decode(modelNametoType["ContactModel"]!,from: data)

但我得到错误

Cannot invoke ‘decode’ with an argument list of type (Codable.Type,
from: Data)

我究竟做错了什么?任何帮助表示赞赏

修复类型有效,并解决任何本地请求,但不能解决远程请求.

let result = try! decoder.decode(ContactModel.self,from: data)

联系型号:

struct ContactModel: Codable {
    var name : String
}

对于远程请求,我会有这样的函数

func buildAnswer(keys: [String]) -> Data {

        var result = [String:Codable]()
        for key in keys {
            let data = CRUD.shared.restoreKey(key: key)
            let item = try decoder.decode(modelNametoType[key]!,from: data)
            result[key] = item
        }
        return try encoder.encode(result)
    }

…如果我解决了解码问题.任何帮助赞赏.

解决方法

Codable API围绕编码和解码为具体类型而构建.但是,你想要的往返不应该知道任何具体的类型;它只是将异构JSON值连接成一个JSON对象.

因此,在这种情况下,JSONSerialization是一个更好的工具,因为它处理Any:

import Foundation

// I would consider lifting your String keys into their own type btw.
func buildAnswer(keys: [String]) throws -> Data {

  var result = [String: Any](minimumCapacity: keys.count)

  for key in keys {
    let data = CRUD.shared.restoreKey(key: key)
    result[key] = try JSONSerialization.jsonObject(with: data)
  }
  return try JSONSerialization.data(withJSONObject: result)
}

话虽如此,您仍然可以使用JSONDecoder / JSONEncoder进行此操作 – 但它需要相当多的类型擦除样板.

例如,我们需要一个符合encodable的包装类型,如encodable doesn’t conform to itself

import Foundation

struct AnyCodable : encodable {

  private let _encode: (Encoder) throws -> Void

  let base: Codable
  let codableType: AnyCodableType

  init<Base : Codable>(_ base: Base) {
    self.base = base
    self._encode = {
      var container = $0.singleValueContainer()
      try container.encode(base)
    }
    self.codableType = AnyCodableType(type(of: base))
  }

  func encode(to encoder: Encoder) throws {
    try _encode(encoder)
  }
}

我们还需要一个包装器来捕获可用于解码的具体类型:

struct AnyCodableType {

  private let _decodeJSON: (JSONDecoder,Data) throws -> AnyCodable
  // repeat for other decoders...
  // (unfortunately I don't believe there's an easy way to make this generic)
  //

  let base: Codable.Type

  init<Base : Codable>(_ base: Base.Type) {
    self.base = base
    self._decodeJSON = { decoder,data in
      AnyCodable(try decoder.decode(base,from: data))
    }
  }

  func decode(from decoder: JSONDecoder,data: Data) throws -> AnyCodable {
    return try _decodeJSON(decoder,data)
  }
}

我们不能简单地将Decodable.Type传递给JSONDecoder

func decode<T : Decodable>(_ type: T.Type,from data: Data) throws -> T

当T是协议类型时,type:参数采用.Protocol元类型,而不是.Type元类型(有关详细信息,请参阅this Q&A).

我们现在可以为我们的键定义一个类型,使用一个modelType属性返回一个可以用来解码JSON的AnyCodableType:

enum ModelName : String {

  case contactModel = "ContactModel"
  case anythingModel = "AnythingModel"

  var modelType: AnyCodableType {
    switch self {
    case .contactModel:
      return AnyCodableType(ContactModel.self)
    case .anythingModel:
      return AnyCodableType(AnythingModel.self)
    }
  }
}

然后为往返做这样的事情:

func buildAnswer(keys: [ModelName]) throws -> Data {

  let decoder = JSONDecoder()
  let encoder = JSONEncoder()

  var result = [String: AnyCodable](minimumCapacity: keys.count)

  for key in keys {
    let rawValue = key.rawValue
    let data = CRUD.shared.restoreKey(key: rawValue)
    result[rawValue] = try key.modelType.decode(from: decoder,data: data)
  }
  return try encoder.encode(result)
}

这可能更好地设计为使用Codable而不是它(可能是一个表示您发送到服务器的JSON对象的结构,并使用键路径与缓存层交互),但不知道更多有关CRUD.shared和你如何使用它;很难说.

相关文章

软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘...
现实生活中,我们听到的声音都是时间连续的,我们称为这种信...
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿...
【Android App】实战项目之仿抖音的短视频分享App(附源码和...
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至...
因为我既对接过session、cookie,也对接过JWT,今年因为工作...