0

Say I have to send this data to the server:

struct Event: Codable {
    let title: String
    let params: [String:Any]? //Not allowed
}

So for instance, these events could look like any of these:

let event = Event(title: "cat was pet", params:["age": 7.2])
let event = Event(title: "cat purred", params:["time": 9])
let event = Event(title: "cat drank", params:["water": "salty", "amount": 3.3])

I need the ability to send any arbitrary amount of key/value pairs as params to the event. Is this even possible with Codable? If not, how would I encode params as a json string?

soleil
  • 12,133
  • 33
  • 112
  • 183
  • 1
    Can the params' values _really_ be `Any`? E.g. Can I pass a UIViewController as a param value? You'll most likely want to constrain it to a narrower set of JSON-supported (or Encodable) types – Alexander Jul 11 '23 at 15:00
  • Probably just strings, ints, and floats. – soleil Jul 11 '23 at 15:07
  • 1
    Was there a problem with the JSON type I discussed on the last question? https://stackoverflow.com/questions/65901928/swift-jsonencoder-encoding-class-containing-a-nested-raw-json-object-literal/65902852#65902852 For a version with basically this exact syntax, see https://github.com/rnapier/RNAJSON/tree/main/Sources/JSONValue – Rob Napier Jul 11 '23 at 15:21
  • @RobNapier I don't quite follow that answer. How would my `Event` struct need to change to accommodate that? Could you post an answer here? – soleil Jul 11 '23 at 15:37

2 Answers2

1

Using a type like JSONValue, it would look almost identical to what you describe:

import JSONValue

struct Event: Codable {
    let title: String
    let params: JSONValue?  // JSONValue rather than [String: Any]
}

// All the rest is the same
let event1 = Event(title: "cat was pet", params:["age": 7.2])
let event2 = Event(title: "cat purred", params:["time": 9])
let event3 = Event(title: "cat drank", params:["water": "salty", "amount": 3.3])

There's a lot of helper code in JSONValue, but at its heart is just an enum, as described in Swift/JSONEncoder: Encoding class containing a nested raw JSON object literal:

public enum JSONValue {
    case string(String)
    case number(digits: String)
    case bool(Bool)
    case object([String: JSONValue])
    case array([JSONValue])
    case null
}

Everything else is just helpers to encode/decode, conform to ExpressibleBy... protocols, etc.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Appreciate it. Going to go with `JSONSerialization` for brevity here, but this is good to know. – soleil Jul 11 '23 at 16:40
0

Codable is magic if all types conform to Codable, but in your case I suggest traditional JSONSerialization. Add a computed property dictionaryRepresentation

struct Event: {
    let title: String
    let params: [String:Any]

    var dictionaryRepresentation: [String:Any] {
        return ["title":title,"params":params] 
    }
}

then encode the event

let data = try JSONSerialization.data(withJSONObject: event.dictionaryRepresentation)

This is less expensive/cumbersome than forcing Any to become Codable (no offense, Rob).

vadian
  • 274,689
  • 30
  • 353
  • 361