Reputation: 2188
I am trying to make an API call that takes a JSON request body like so:
[
{ "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 }
]
I have some enums for the op
and path
values:
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"
}
In this answer, I found you have to use a protocol to enable creation of array of generic structs, so I have the following protocol and struct:
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
}
And here is where the encoding takes place:
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"
)
The issue is that when I try running encoder.encode(arr)
, I get the following error: Value of protocol type 'UserChangeProto' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
.
My question is, how can I get around this error? Or in other words, what is the simplest way to encode an array of generic structs?
Edit: So it looks like this is an issue with the Swift language itself that the Swift team is looking into. I am not sure how to proceed here...
Upvotes: 0
Views: 401
Reputation: 3857
You might find a type-erased encodable useful: https://github.com/Flight-School/AnyCodable
Using AnyEncodable from the above:
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)
gives what you would expect
Upvotes: 1