0

I am trying to construct the following JSON using Swift, since this is working when I test using Postman. I will need to be able to change the values of the parameters so am trying to avoid just building a string:

{
   "item": {
      "record": "10",
      "field_name": "orientation_forward",
      "redcap_repeat_instance": "1",
      "redcap_repeat_instrument": "range_of_motion_result",
      "value": "4",
      "redcap_event_name": []
   }
}

Here is my attempt to do so, but it does not even seem to be valid JSON when I test it:

var record = "10"
var field_name = "orientation_forward"
var repeat_instance = "1"
var repeat_instrument = "range_of_motion_result"
var value = "4"
var event: [String] = [] // not sure how else to pass in '[]' when empty

    let dataObject: [String: Any] = [
        "item":
            ["record": record,
             "field_name": field_name,
             "redcap_repeat_instance": repeat_instance,
             "redcap_repeat_instrument": repeat_instrument,
             "value": value,
             "redcap_event_name": event]
    ]
            
    if let jsonData = try? JSONSerialization.data(withJSONObject: dataObject, options: .init(rawValue: 0)) as? Data
    {
        // Check if it worked...
        print(String(data: jsonData!, encoding: .utf8)!)
        let jsonTest = JSONSerialization.isValidJSONObject(jsonData) // false!
        print(jsonTest)

    }

All help graciously received. Thanks in advance.

Willeke
  • 14,578
  • 4
  • 19
  • 47
davwillev
  • 83
  • 10

1 Answers1

2

The best way forward is to use Codable here, a little more work to get started but much cleaner code in the end.

So first we create a struct that conforms to Codable

struct Item: Codable {
    let record: String
    let fieldName: String
    let repeatInstance: String
    let repeatInstrument: String
    let value: String
    let event: [String]

    enum CodingKeys: String, CodingKey {
        case record
        case fieldName = "field_name"
        case repeatInstance = "redcap_repeat_instance"
        case repeatInstrument = "redcap_repeat_instrument"
        case value
        case event = "redcap_event_name"
    }
}

The CodingKeys enum is used to get the correct field names

and then it is used like this

let item = Item(record: "10", fieldName: "orientation_forward", repeatInstance: "1", repeatInstrument: "range_of_motion_result", value: "4", event: [])

do {
    let data = try JSONEncoder().encode(["item": item])
    
    if let s = String(data: data, encoding: .utf8) { print(s) }
} catch {
    print(error)
}

{"item":{"field_name":"orientation_forward","value":"4","redcap_event_name":[],"redcap_repeat_instrument":"range_of_motion_result","record":"10","redcap_repeat_instance":"1"}}

Some prefer to use custom types all the way and if so you can create a top level struct instead of using a dictionary

struct ItemContainer: Codable {
    let item: Item
}

Then the encoding would change to something like

let data = try JSONEncoder().encode(ItemContainer(item: item))

but the end result is the same

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • I see! Thanks very much for that. That is much more elegant than where I was heading. Interesting to see an enum within a data structure - I've not seen that before. Also, I presume that I just replace the values in the 'item' constant with the variables if I need to change the values? – davwillev Jan 22 '22 at 14:44
  • 1
    Yes you create a new Item object with the values you need each time you want to encode (and send it) – Joakim Danielson Jan 22 '22 at 14:46
  • Hmm - strangely, I'm getting the `JSONSerialization.isValidJSONObject(data)` evaluating as false – davwillev Jan 22 '22 at 14:59
  • It's not relevant if your encoding code works fine and doesn't throw an error – Joakim Danielson Jan 22 '22 at 15:07