首页 > 解决方案 > 对泛型结构数组进行编码

问题描述

我正在尝试进行一个 API 调用,该调用采用 JSON 请求主体,如下所示:

[
  { "op": "replace", "path": "/info/name", "value": "TestName" },
  { "op": "replace", "path": "/info/number", "value": 100 },
  { "op": "replace", "path": "/info/location", "value": ["STATE", "CITY"] },
  { "op": "replace", "path": "/privacy/showLocation", "value": true }
]

我有一些oppath值的枚举:

enum ChangeOp: String, Encodable {
  case replace
  case append
}

enum ChangePath: String, Encodable {
  case name = "/info/name"
  case number = "/info/number"
  case location = "/info/location"
  case showLocation = "/privacy/showLocation"
}

这个答案中,我发现您必须使用协议来创建通用结构数组,所以我有以下协议和结构:

protocol UserChangeProto {
  var op: ChangeOp { get }
  var path: ChangePath { get }
}

struct UserChange<ValueType: Encodable>: Encodable, UserChangeProto {
  let op: ChangeOp
  let path: ChangePath
  let value: ValueType
}

这是编码发生的地方:

func encodeChanges(arr: [UserChangeProto]) -> String? {
  let encoder = JSONEncoder()
  guard let jsonData = try? encoder.encode(arr) else {
    return nil
  }
  return String(data: jsonData, encoding: String.Encoding.utf8)
}

func requestUserChanges(changes: String) {
  print(changes)

  // make API request ...
}

requestUserChanges(changes:
  encodeChanges(arr: [
    UserChange(op: .replace, path: .name, value: "TestName"),
    UserChange(op: .replace, path: .number, value: 100),
    UserChange(op: .replace, path: .location, value: ["STATE", "CITY"]),
    UserChange(op: .replace, path: .showLocation, value: true)
  ]) ?? "null"
)

问题是当我尝试运行时encoder.encode(arr),我收到以下错误:Value of protocol type 'UserChangeProto' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols.

我的问题是,我怎样才能绕过这个错误?或者换句话说,对泛型结构数组进行编码的最简单方法是什么

编辑:所以看起来这是Swift 团队正在研究的 Swift 语言本身的问题。我不知道如何在这里进行...

标签: swiftgenericsjsonencoder

解决方案


您可能会发现类型擦除编码很有用:https ://github.com/Flight-School/AnyCodable

使用上面的 AnyEncodable:

struct Change<V: Encodable>: Encodable {
    enum Op: String, Encodable {
        case replace
        case append
    }
    
    enum Path: String, Encodable {
        case name = "/info/name"
        case number = "/info/number"
        case location = "/info/location"
        case showLocation = "/privacy/showLocation"
    }
    
    var op: Op
    var path: Path
    var value: V
}

let encoder = JSONEncoder()
let changes: [Change<AnyEncodable>] = [
    Change(op: .append, path: .name, value: "Foo"),
    Change(op: .replace, path: .number, value: 42)
]

let r = try? encoder.encode(changes)

String(data: r!, encoding: .utf8)

给出你所期望的


推荐阅读