'Build a json array from multiple properties of a custom type in Swift
After being pointed towards using Codable to format json in Swift, I am trying to add multiple results to a json array. I am unfortunately struggling to work out how to build this json array for a set of results that contains multiple properties.
Here is an example of my required json array for two 'sets' of results (N.B. the number of sets can vary in the data recorded):
[{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"1","field_name":"orientation_forward","value":"2"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"1","field_name":"start_forward","value":"3.45"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"1","field_name":"finish_forward","value":"5.29"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"1","field_name":"minimum_forward","value":"7.81"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"1","field_name":"maximum_forward","value":"9.20"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"1","field_name":"range_forward","value":"2.39"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"2","field_name":"orientation_forward","value":"1"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"2","field_name":"start_forward","value":"1.89"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"2","field_name":"finish_forward","value":"4.20"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"2","field_name":"minimum_forward","value":"8.26"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"2","field_name":"maximum_forward","value":"46.82"},
{"record":"10","redcap_repeat_instrument":"range_of_motion_result","redcap_repeat_instance":"2","field_name":"range_forward","value":"2.98"}]
And below is my Swift code. The results arrive as an argument within an array of custom type ORKRangeOfMotionResult (from ResearchKit with each set of results giving me a value for orientation, start, finish, minumum, maximum and range properties). Each set of ORKRangeOfMotionResult is also accompanied by an identifier (variableIdentifier), pulled in as another result that I can use to ensure I know which set is which.
I am currently using a loop to extract individual values for each ORKRangeOfMotionResult property. After this, it is relatively easy to add one result to the json array [items] using the variableIdentifier in if statements (as can be seen below for start) but I don't know how to add all properties from a set of results to the array. I'm guessing a loop, but haven't figured out how as yet.
func buildJsonArrayOfResults(_ rangeOfMotionResults: [ORKRangeOfMotionResult], withIdentifier variableIdentifiers: [String]) {
let record = getIDNumber().stringValue
let repeat_instance = getRepeatInstance().stringValue
let repeat_instrument = "range_of_motion_result"
let event = getEventName()
var fieldName: String?
var identifier: String?
var stringData: String?
var value: String?
var items: [Item] = []
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"
}
}
var setNumber: Int = 0
while setNumber < rangeOfMotionResults.count {
setNumber += 1
// obtain results for each set of movements as a string
let orientation = String(rangeOfMotionResults[setNumber - 1].orientation)
let start = String(rangeOfMotionResults[setNumber - 1].start)
let finish = String(rangeOfMotionResults[setNumber - 1].finish)
let minimum = String(rangeOfMotionResults[setNumber - 1].minimum)
let maximum = String(rangeOfMotionResults[setNumber - 1].maximum)
let range = String(rangeOfMotionResults[setNumber - 1].range)
// assign one value and its redcap field name
identifier = variableIdentifiers[setNumber - 1]
if identifier!.contains("forward") {
fieldName = "start_forward"
value = start
} else if identifier!.contains("backward") {
fieldName = "start_backward"
value = start
} else if identifier!.contains("left.bending") {
fieldName = "start_left_bending"
value = start
} else if identifier!.contains("right.bending") {
fieldName = "start_right_bending"
value = start
} else if identifier!.contains("left.rotation") {
fieldName = "start_left_rotation"
value = start
} else if identifier!.contains("right.rotation") {
fieldName = "start_left_rotation"
value = start
}
let item = Item(record: record, fieldName: fieldName!, repeatInstance: String(repeat_instance), repeatInstrument: repeat_instrument, value: value!, event: event)
items.append(item)
}
do {
let data = try JSONEncoder().encode(items)
stringData = String(data: data, encoding: .utf8)
print(stringData!)
} catch {
print(error)
}
// now do something with stringData
}
All hep graciously received. Thank you in advance.
Solution 1:[1]
According to the given scenario you need only these properties
func buildJsonArrayOfResults(_ rangeOfMotionResults: [ORKRangeOfMotionResult], withIdentifier variableIdentifiers: [String]) {
let record = getIDNumber().stringValue
let repeatInstance = getRepeatInstance().stringValue
let repeatInstrument = "range_of_motion_result"
let event = getEventName()
var items: [Item] = []
struct Item: Codable { ... }
And as the order of the identifiers match the order of the result array the recommended way to enumerate an array by index and element is enumerated
for (index, result) in rangeOfMotionResults.enumerated() {
let identifier = variableIdentifiers[index]
and it seems that finish, minimum, maximum and range are unused
// obtain results for each set of movements as a string
let orientation = String(result.orientation)
let value = String(result.start)
a switch expression is swiftier than an if - else chain
let fieldName : String
switch identifier {
case let id where id.contains("forward"): fieldName = "start_forward"
case let id where id.contains("backward"): fieldName = "start_backward"
case let id where id.contains("left.bending"): fieldName = "start_left_bending"
case let id where id.contains("right.bending"): fieldName = "start_right_bending"
case let id where id.contains("left.rotation"): fieldName = "start_left_rotation"
case let id where id.contains("right.rotation"): fieldName = "start_left_rotation"
default: fieldName = "unknown"
}
let item = Item(record: record, fieldName: fieldName, repeatInstance: repeatInstance, repeatInstrument: repeatInstrument, value: value, event: event)
items.append(item)
}
do {
let data = try JSONEncoder().encode(items)
let stringData = String(data: data, encoding: .utf8)!
print(stringData)
// now do something with stringData
} catch {
print(error)
}
}
I recommend also to insert this line before the for loop
assert(rangeOfMotionResults.count == variableIdentifiers.count, "The size of rangeOfMotionResults and variableIdentifiers must be equal")
It checks if the sizes of both arrays are equal, the expression is ignored in a Release Build.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
