Кодирование массива общих структур

Я пытаюсь выполнить вызов 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 }
]

У меня есть несколько перечислений для значений op и path:

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 изучает. Я не знаю, как здесь действовать ...


person rgajrawala    schedule 18.03.2021    source источник


Ответы (1)


Вы можете найти кодируемый код со стиранием типа: 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)

дает то, что вы ожидаете

person Shadowrun    schedule 18.03.2021
comment
Оказывается, вы также можете использовать связанные перечисления значений, которые более элегантны, чем наличие структуры с членом для каждого типа. Я обновил суть, чтобы показать это. Вы также можете использовать вычисляемое свойство в перечислении для закрепления ключа и типа значения, чтобы разработчики не могли случайно использовать неправильный тип. Перечисления намного мощнее, чем я думал вначале, придется взглянуть на них еще раз! - person rgajrawala; 19.03.2021