json - 如何在 Swift 中将嵌套的 JSON 字符串解析为对象?

标签 json swift postgresql swift4

我有以下数据结构

struct Symbol : Codable {
    let id:Int
    let type:String
    let properties:[String:String]?
}

struct Event : Codable {
    let event:String
    let timestamp:Int
    let symbol:Symbol?
}

struct LogRow : Codable {
    let id:Int
    let user_id:String
    let question_id:String
    let actions:[Event]
    let timestamp:Double
}

和下面的 JSON 数组

[
  {
    "id": 26535754,
    "user_id": "qhv1i39wsmbkzhjiffk1rrsg",
    "question_id": "\"trapdoor|186752c1-948e-4c15-b3df-7d39a99fe9d6\"",
    "actions": "[{\"event\": \"OPEN\", \"timestamp\": 1499802241640}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 15, \"type\": \"Fn\", \"properties\": {\"name\": \"cos\", \"allowSubscript\": true, \"innerSuperscript\": true}}, \"timestamp\": 1499802243567}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 14, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 15, \"type\": \"Fn\", \"properties\": {\"name\": \"cos\", \"allowSubscript\": true, \"innerSuperscript\": true}}, \"timestamp\": 1499802243567, \"dockingPoint\": \"right\"}, {\"event\": \"DOCK_SYMBOL\", \"parent\": {\"id\": 13, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 15, \"type\": \"Fn\", \"properties\": {\"name\": \"cos\", \"allowSubscript\": true, \"innerSuperscript\": true}}, \"timestamp\": 1499802243699, \"dockingPoint\": \"denominator\"}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 16, \"type\": \"Num\", \"properties\": {\"significand\": \"60\"}}, \"timestamp\": 1499802244570}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 15, \"type\": \"Fn\", \"properties\": {\"name\": \"cos\", \"allowSubscript\": true, \"innerSuperscript\": true}}, \"symbol\": {\"id\": 16, \"type\": \"Num\", \"properties\": {\"significand\": \"60\"}}, \"timestamp\": 1499802244570, \"dockingPoint\": \"argument\"}, {\"event\": \"DROP_SYMBOL\", \"symbol\": {\"id\": 16, \"type\": \"Num\", \"properties\": {\"significand\": \"60\"}}, \"timestamp\": 1499802245281}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 13, \"type\": \"Fraction\"}, \"timestamp\": 1499802245845}, {\"event\": \"TRASH_SYMBOL\", \"symbol\": {\"id\": 13, \"type\": \"Fraction\"}, \"timestamp\": 1499802246826}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 16, \"type\": \"Num\", \"properties\": {\"significand\": \"60\"}}, \"timestamp\": 1499802247468}, {\"event\": \"TRASH_SYMBOL\", \"symbol\": {\"id\": 16, \"type\": \"Num\", \"properties\": {\"significand\": \"60\"}}, \"timestamp\": 1499802248360}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802249161}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 14, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802249161, \"dockingPoint\": \"denominator\"}, {\"event\": \"DOCK_SYMBOL\", \"parent\": {\"id\": 14, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802249289, \"dockingPoint\": \"denominator\"}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802249797}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 14, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802249797, \"dockingPoint\": \"denominator\"}, {\"event\": \"DOCK_SYMBOL\", \"parent\": {\"id\": 14, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802249906, \"dockingPoint\": \"denominator\"}, {\"event\": \"DRAG_POTENTIAL_SYMBOL\", \"symbol\": {\"id\": 20, \"type\": \"Num\", \"properties\": {\"significand\": \"4\"}}, \"timestamp\": 1499802251451}, {\"event\": \"DROP_POTENTIAL_SYMBOL\", \"symbol\": {\"id\": 20, \"type\": \"Num\", \"properties\": {\"significand\": \"4\"}}, \"timestamp\": 1499802254229}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802255231}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 14, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802255231, \"dockingPoint\": \"denominator\"}, {\"event\": \"TRASH_SYMBOL\", \"symbol\": {\"id\": 19, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802256593}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 20, \"type\": \"Num\", \"properties\": {\"significand\": \"4\"}}, \"timestamp\": 1499802257376}, {\"event\": \"DOCK_SYMBOL\", \"parent\": {\"id\": 14, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 20, \"type\": \"Num\", \"properties\": {\"significand\": \"4\"}}, \"timestamp\": 1499802258062, \"dockingPoint\": \"denominator\"}, {\"event\": \"CLOSE\", \"timestamp\": 1499802259093}]",
    "timestamp": 1.499802259277E9
  },
  {
    "id": 26535718,
    "user_id": "qhv1i39wsmbkzhjiffk1rrsg",
    "question_id": "\"trapdoor|186752c1-948e-4c15-b3df-7d39a99fe9d6\"",
    "actions": "[{\"event\": \"OPEN\", \"timestamp\": 1499802175061}, {\"event\": \"DRAG_POTENTIAL_SYMBOL\", \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802178936}, {\"event\": \"DOCK_POTENTIAL_SYMBOL\", \"parent\": {\"id\": 7, \"type\": \"Symbol\", \"properties\": {\"letter\": \"g\"}}, \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802183785, \"dockingPoint\": \"subscript\"}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802184864}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 7, \"type\": \"Symbol\", \"properties\": {\"letter\": \"g\"}}, \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802184865, \"dockingPoint\": \"subscript\"}, {\"event\": \"DOCK_SYMBOL\", \"parent\": {\"id\": 6, \"type\": \"Symbol\", \"properties\": {\"letter\": \"m\"}}, \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802185857, \"dockingPoint\": \"subscript\"}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802186710}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 6, \"type\": \"Symbol\", \"properties\": {\"letter\": \"m\"}}, \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802186710, \"dockingPoint\": \"subscript\"}, {\"event\": \"DROP_SYMBOL\", \"symbol\": {\"id\": 10, \"type\": \"Fraction\"}, \"timestamp\": 1499802188430}, {\"event\": \"DRAG_POTENTIAL_SYMBOL\", \"symbol\": {\"id\": 11, \"type\": \"Fraction\"}, \"timestamp\": 1499802194665}, {\"event\": \"DROP_POTENTIAL_SYMBOL\", \"symbol\": {\"id\": 11, \"type\": \"Fraction\"}, \"timestamp\": 1499802195427}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 6, \"type\": \"Symbol\", \"properties\": {\"letter\": \"m\"}}, \"timestamp\": 1499802196167}, {\"event\": \"DOCK_SYMBOL\", \"parent\": {\"id\": 11, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 6, \"type\": \"Symbol\", \"properties\": {\"letter\": \"m\"}}, \"timestamp\": 1499802197167, \"dockingPoint\": \"numerator\"}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 8, \"type\": \"Fn\", \"properties\": {\"name\": \"cos\", \"allowSubscript\": true, \"innerSuperscript\": true}}, \"timestamp\": 1499802198247}, {\"event\": \"UNDOCK_SYMBOL\", \"parent\": {\"id\": 7, \"type\": \"Symbol\", \"properties\": {\"letter\": \"g\"}}, \"symbol\": {\"id\": 8, \"type\": \"Fn\", \"properties\": {\"name\": \"cos\", \"allowSubscript\": true, \"innerSuperscript\": true}}, \"timestamp\": 1499802198247, \"dockingPoint\": \"right\"}, {\"event\": \"DOCK_SYMBOL\", \"parent\": {\"id\": 11, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 8, \"type\": \"Fn\", \"properties\": {\"name\": \"cos\", \"allowSubscript\": true, \"innerSuperscript\": true}}, \"timestamp\": 1499802199971, \"dockingPoint\": \"right\"}, {\"event\": \"DRAG_START\", \"symbol\": {\"id\": 11, \"type\": \"Fraction\"}, \"timestamp\": 1499802201551}, {\"event\": \"DROP_SYMBOL\", \"symbol\": {\"id\": 11, \"type\": \"Fraction\"}, \"timestamp\": 1499802202638}, {\"event\": \"DRAG_POTENTIAL_SYMBOL\", \"symbol\": {\"id\": 12, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802209025}, {\"event\": \"DOCK_POTENTIAL_SYMBOL\", \"parent\": {\"id\": 11, \"type\": \"Fraction\"}, \"symbol\": {\"id\": 12, \"type\": \"Num\", \"properties\": {\"significand\": \"2\"}}, \"timestamp\": 1499802210771, \"dockingPoint\": \"denominator\"}, {\"event\": \"CLOSE\", \"timestamp\": 1499802212398}]",
    "timestamp": 1.49980221259E9
  }
]

JSON数据来自数据库,通过DataGrip导出的 JSON 导出。数据库字段是JSON Blob类型(是Postgres的,所以没问题)。查询类似于:

SELECT
  ...
  event_details->>'actions' AS actions,
  ...
FROM yadda_yadda...;

以防有用(是的,我也试过单箭头版本,没什么区别)。

我的问题是如何让 Swift 将 actions 字符串作为 JSON 对象解析为 [Event]?或者,是否有一种方法可以使 DataGrip(或 postgres)将该字段导出为与其容器一样的对象而不是序列化对象?

编辑 我像这样更改了 LogRow

struct LogRow : Codable {
    let id:Int
    let user_id:String
    let question_id:String
    let actions:[Event]
    let timestamp:Double

    enum CodingKeys: String, CodingKey {
        case id
        case user_id
        case question_id
        case actions
        case timestamp
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int.self, forKey: .id)
        user_id = try values.decode(String.self, forKey: .user_id)
        question_id = try values.decode(String.self, forKey: .question_id)
        timestamp = try values.decode(Double.self, forKey: .timestamp)

        let actions_string = try values.decode(String.self, forKey: .actions)
        let actions_data = actions_string.data(using: .utf8)!
        actions = try JSONDecoder().decode([Event].self, from: actions_data)
    }
}

现在我遇到了与之前类似的错误,但显然是在 Event 上,而不是在 LogRow 上。

fatal error: Error raised at top level:
Swift.DecodingError.typeMismatch(Swift.String,
Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in
_12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 1", intValue:
Optional(1)), tree_builder.Event.(CodingKeys in
_D5964B2C6A943A986EE24818C2C63D9B).symbol, tree_builder.Symbol.
(CodingKeys in _D5964B2C6A943A986EE24818C2C63D9B).properties,
Swift._DictionaryCodingKey(stringValue: "allowSubscript", intValue:
nil)], debugDescription: "Expected to decode String but found a number
instead.", underlyingError: nil)): file
/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-
900.0.65/src/swift/stdlib/public/core/ErrorType.swift, line 187

最佳答案

可以使用optional chaining,这是Apple给出的解决方案:https://developer.apple.com/swift/blog/?id=37

还有一个库可以为你做这件事,比如 https://github.com/Hearst-DD/ObjectMapper , 或 https://github.com/tristanhimmelman/AlamofireObjectMapper

这是一个关于如何做到这一点的基本示例,但您应该重构并查看我提供的链接:

guard let event = jsonObject["event"] as? String, let timestamp = jsonObject["timestamp"] as? Int else {return}
myEvent.event = event
myEvent.timestamp = timestamp 
if let symbol = jsonObject["symbol"] as? [String:Any] {
    guard let symbolId = symbol["id"] as? Int, let type = symbol["type"] else {return}
    let mySymbol = symbol()
    mySymbol.id = id
    mySymbol.type = type
}

编辑

如果您使用的是 swift 4 和 Codable 协议(protocol),那应该非常简单,您只需要将嵌套对象也设为 Codable 即可!然后只是:

try decoder.decode(yourType.self, from: yourJsonString)

如果您正确使用 CodingKeys,它应该可以做到。

关于json - 如何在 Swift 中将嵌套的 JSON 字符串解析为对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46426756/

相关文章:

javascript - 未捕获( promise )TypeError : Cannot set property 'status'

使用 XCTAssertThrows 模拟进行 Swift 单元测试

ios - SceneKit 不会在 Xcode 12 中填满屏幕

python - 如何在 Python 和 postgresql 中使用 inet 类型?

sql - 如何更改查询中的行信息?

javascript - 创建多维数组和JSON解析

c# - 通过C#中的TCP套接字将鼠标坐标发送到Unity Client

ios - 在两个同时显示的 View Controller 之间传递数据的最简单方法是什么?

sql - 连续出现次数及其长度(高级间隙和孤岛问题)

arrays - 快速将字符串 json 转换为数组