如何将Swift JSONDecode与动态类型一起使用? [英] How to use Swift JSONDecode with dynamic types?

查看:129
本文介绍了如何将Swift JSONDecode与动态类型一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序具有本地缓存​​,可以从服务器发送模型或从服务器接收模型.因此,我决定构建一个映射[String:Codable.Type],实质上是为了能够解码我在本地创建的通用缓存或从服务器接收的通用缓存上的所有内容.

My App has a local cache and sends/receives models from/to the server. So I decided to build a map [String : Codable.Type], essentially to be able to decode anything I have on this generic cache either created locally or received from server.

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

无论我在App上创建的内容如何,​​我都可以成功进行编码并将其存储在缓存中,如下所示:

Whatever I create on the App I can encode successfully and store on cache like this:

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)

但是我得到了错误:

无法使用类型为(Codable.Type, 来自:数据)

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

我做错了什么?感谢您的帮助

What am I doing wrong? Any help is appreciated

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

Fixing the type works, and solves any local request, but not a remote request.

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

联系方式:

struct ContactModel: Codable {
    var name : String
}

对于远程请求,我将具有以下功能:

For remote requests I would have a function like this:

    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)
    }

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

...if I solve the decode issue. Any help appreciated.

推荐答案

Codable API围绕着具体类型的编码和解码而构建.但是,您在此处想要进行的往返操作不必了解任何具体类型;只是将异构JSON值连接到JSON对象中.

The Codable API is built around encoding from and decoding into concrete types. However, the round-tripping you want here shouldn't have to know about any concrete types; it's merely concatenating heterogenous JSON values into a JSON object.

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

Therefore, JSONSerialization is a better tool for the job in this case, as it deals with 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做到这一点–但是它需要大量的类型擦除样板.


That being said, you could still make this with JSONDecoder/JSONEncoder – however it requires quite a bit of type-erasing boilerplate.

例如,我们需要一个符合Encodable的包装类型,因为Encodable 不符合其自身:

For example, we need a wrapper type that conforms to Encodable, as 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)
  }
}

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

We also need a wrapper to capture a concrete type that can be used for decoding:

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

We cannot simply pass a Decodable.Type to JSONDecoder's

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

就像T是协议类型一样,type:参数采用.Protocol元类型,而不是.Type元类型(请参阅

as when T is a protocol type, the type: parameter takes a .Protocol metatype, not a .Type metatype (see this Q&A for more info).

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

We can now define a type for our keys, with a modelType property that returns an AnyCodableType that we can use for decoding JSON:

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)
    }
  }
}

,然后为往返执行以下操作:

and then do something like this for the round-tripping:

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以及如何使用它的更多信息;很难说.

This probably could be designed better to work with Codable rather than against it (perhaps a struct to represent the JSON object you send to the server, and use key paths to interact with the caching layer), but without knowing more about CRUD.shared and how you use it; it's hard to say.

这篇关于如何将Swift JSONDecode与动态类型一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆