从 Swift 中自定义类型的多个属性构建 json 数组

Build a json array from multiple properties of a custom type in Swift

在被指向使用 Codable 格式化 Swift 中的 json 之后,我试图将多个结果添加到 json 数组。不幸的是,我正在努力研究如何为包含多个属性的一组结果构建这个 json 数组。

这是我需要的 json 数组的示例,用于两个 'sets' 结果(N.B。集合的数量可能因记录的数据而异):

[{"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"}]

下面是我的 Swift 代码。结果作为自定义类型数组 ORKRangeOfMotionResult 中的一个参数到达(来自 ResearchKit,每组结果给我一个值 orientationstartfinishminumummaximumrange 属性)。每组 ORKRangeOfMotionResult 还附带一个标识符 (variableIdentifier),作为另一个结果提取,我可以使用它来确保我知道哪个集合是哪个。

我目前正在使用循环为每个 ORKRangeOfMotionResult 属性 提取单独的值。在此之后,使用 if 语句中的 variableIdentifier 将一个结果添加到 json 数组 [items] 相对容易(如下面的 start ) 但我不知道如何将一组结果中的所有属性添加到数组中。我猜是一个循环,但还没有弄清楚是怎么回事。

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

}

所有的帮助都被慷慨地接受了。提前谢谢你。

根据给定的场景,您只需要这些属性

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 { ... }

并且由于标识符的顺序与结果数组的顺序相匹配,推荐的枚举数组索引和元素的方法是enumerated

    for (index, result) in rangeOfMotionResults.enumerated() {
        let identifier = variableIdentifiers[index]

并且 finishminimummaximumrange 似乎未被使用

       // obtain results for each set of movements as a string
        let orientation = String(result.orientation)
        let value = String(result.start)       

switch 表达式 if - else

更快
        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)
    }
}

我还建议在 for 循环之前插入这一行

assert(rangeOfMotionResults.count == variableIdentifiers.count, "The size of rangeOfMotionResults and variableIdentifiers must be equal")

它检查两个数组的大小是否相等,在 Release Build 中忽略该表达式。

感谢@vadian 的回答,我能够通过添加一个新的 struct 来完全解决这个问题,其中包含一个 key-value pair 和一个标识符(以及 array 这些 'trios') 然后添加第二个 for-in loop。我确信我的代码可以进一步优化(这会很有趣),但我很高兴(也很感激)现在正在按照我想要的方式构建 json 数组。这是我的最终解决方案:

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 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"
        }
    }
        
    struct Trio {
        let key: String
        let value: String
        let identifier: String
    }
    var trios = [Trio]()
        
    assert(rangeOfMotionResults.count == variableIdentifiers.count, "The size of rangeOfMotionResults and variableIdentifiers must be equal")
        
    for (index, result) in rangeOfMotionResults.enumerated() {
        identifier = variableIdentifiers[index]
            
        // add key-value pairs with their identifiers to array
        let orientation = String(result.orientation)
        trios.append(Trio(key: "orientation", value: orientation, identifier: identifier!))
            
        let start = String(result.start)
        trios.append(Trio(key: "start", value: start, identifier: identifier!))
            
        let finish = String(result.finish)
        trios.append(Trio(key: "finish", value: finish, identifier: identifier!))
            
        let minimum = String(result.minimum)
        trios.append(Trio(key: "minimum", value: minimum, identifier: identifier!))
            
        let maximum = String(result.maximum)
        trios.append(Trio(key: "maximum", value: maximum, identifier: identifier!))
            
        let range = String(result.range)
        trios.append(Trio(key: "range", value: range, identifier: identifier!))
    }
        
    for (_, result) in trios.enumerated() {
            
        let ident = result.identifier
        value = result.value
            
        let fieldName: String
        switch ident {
        case let id where id.contains("forward"):
            fieldName = result.key + "_forward"
        case let id where id.contains("backward"):
            fieldName = result.key + "_backward"
        case let id where id.contains("left.bending"):
            fieldName = result.key + "_left_bending"
        case let id where id.contains("right.bending"):
            fieldName = result.key + "_right_bending"
        case let id where id.contains("left.rotation"):
            fieldName = result.key + "_left_rotation"
        case let id where id.contains("right.rotation"):
            fieldName = result.key + "_left_rotation"
        default:
            fieldName = result.key + "_unknown"
        }
            
        let item = Item(record: record, fieldName: fieldName, repeatInstance: String(repeatInstance), repeatInstrument: repeatInstrument, value: value!, event: event)
            
        items.append(item)
    }
        
    do {
        let data = try JSONEncoder().encode(items)
        stringData = String(data: data, encoding: .utf8)
        print(stringData!)

        // now do something with stringData

    } catch {
        print(error)
    }
}