编码通用结构数组

Encoding an array of generic structs

我正在尝试进行一个 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 team is looking into。我不确定如何在这里进行...

您可能会发现类型擦除的编码器很有用: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)

如您所愿