Ios 在Swift 5中,有没有一种合理的方法可以将结构未知的嵌套JSON解析为对象或字典?
免责声明:有相当多的类似问题张贴,但似乎没有一个复制我自己的 假设我的应用程序接收到一个来自设计非常糟糕的API的JSON,该JSON看起来像这样:Ios 在Swift 5中,有没有一种合理的方法可以将结构未知的嵌套JSON解析为对象或字典?,ios,json,swift,swift5,Ios,Json,Swift,Swift5,免责声明:有相当多的类似问题张贴,但似乎没有一个复制我自己的 假设我的应用程序接收到一个来自设计非常糟糕的API的JSON,该JSON看起来像这样: { "matches": { "page1": [{ "name": "John", "surname": "Doe", "interests": [{ "id": 13,
{
"matches": {
"page1": [{
"name": "John",
"surname": "Doe",
"interests": [{
"id": 13,
"text": "basketball"
},
{
"id": 37,
"text": "competitive knitting"
},
{
"id": 127,
"text": "romcoms"
}
]
}],
"page2": [{
"name": "Dwayne",
"surname": "Johnson",
"interests": [{
"id": 42,
"text": "sci-fi"
},
{
"id": 255,
"text": "round numbers"
}
]
}]
}
}
struct MatchesData: Codable {
let matches: Matches
}
struct Matches: Codable {
let page1: Page1
let page2: Page2
}
struct Page1: Codable {
let interests: [Interest]
}
struct Page2: Codable {
let interests: [Interest]
}
struct Interest: Codable {
let id: Int
let text: String
}
func handleJSON(_ response: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MatchesData.self, from: response)
// Only here I can start actually working with the data API sent me, it's in decodedData
...
} catch {
// Handle the errors somehow
return
}
}
let json = JSON(parseJSON: jsonString)
let matchesObject = json["matches"]
// I assume the total number of pages is stored in numberOfPages
let interests = (0..<2).flatMap {
matchesObject["page\($0 + 1)"].arrayValue.flatMap {
$0["interests"].arrayValue.map {
// using the same Interest struct as in your question
Interest(id: $0["id"].intValue, text: $0["text"].stringValue)
}
}
}
比如说,如果我想从所有匹配中获得所有兴趣,在本机Swift功能中,我首先必须这样做:
{
"matches": {
"page1": [{
"name": "John",
"surname": "Doe",
"interests": [{
"id": 13,
"text": "basketball"
},
{
"id": 37,
"text": "competitive knitting"
},
{
"id": 127,
"text": "romcoms"
}
]
}],
"page2": [{
"name": "Dwayne",
"surname": "Johnson",
"interests": [{
"id": 42,
"text": "sci-fi"
},
{
"id": 255,
"text": "round numbers"
}
]
}]
}
}
struct MatchesData: Codable {
let matches: Matches
}
struct Matches: Codable {
let page1: Page1
let page2: Page2
}
struct Page1: Codable {
let interests: [Interest]
}
struct Page2: Codable {
let interests: [Interest]
}
struct Interest: Codable {
let id: Int
let text: String
}
func handleJSON(_ response: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MatchesData.self, from: response)
// Only here I can start actually working with the data API sent me, it's in decodedData
...
} catch {
// Handle the errors somehow
return
}
}
let json = JSON(parseJSON: jsonString)
let matchesObject = json["matches"]
// I assume the total number of pages is stored in numberOfPages
let interests = (0..<2).flatMap {
matchesObject["page\($0 + 1)"].arrayValue.flatMap {
$0["interests"].arrayValue.map {
// using the same Interest struct as in your question
Interest(id: $0["id"].intValue, text: $0["text"].stringValue)
}
}
}
然后,我必须以如下方式使用我创建的结构:
{
"matches": {
"page1": [{
"name": "John",
"surname": "Doe",
"interests": [{
"id": 13,
"text": "basketball"
},
{
"id": 37,
"text": "competitive knitting"
},
{
"id": 127,
"text": "romcoms"
}
]
}],
"page2": [{
"name": "Dwayne",
"surname": "Johnson",
"interests": [{
"id": 42,
"text": "sci-fi"
},
{
"id": 255,
"text": "round numbers"
}
]
}]
}
}
struct MatchesData: Codable {
let matches: Matches
}
struct Matches: Codable {
let page1: Page1
let page2: Page2
}
struct Page1: Codable {
let interests: [Interest]
}
struct Page2: Codable {
let interests: [Interest]
}
struct Interest: Codable {
let id: Int
let text: String
}
func handleJSON(_ response: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MatchesData.self, from: response)
// Only here I can start actually working with the data API sent me, it's in decodedData
...
} catch {
// Handle the errors somehow
return
}
}
let json = JSON(parseJSON: jsonString)
let matchesObject = json["matches"]
// I assume the total number of pages is stored in numberOfPages
let interests = (0..<2).flatMap {
matchesObject["page\($0 + 1)"].arrayValue.flatMap {
$0["interests"].arrayValue.map {
// using the same Interest struct as in your question
Interest(id: $0["id"].intValue, text: $0["text"].stringValue)
}
}
}
虽然这种方法可行,但它有两个主要缺点
首先,对于这么简单的任务来说,所有的准备工作和代码都是疯狂的,而且不符合DRY原则
最后,如果您事先不知道JSON的确切结构,这种方法根本不起作用。例如,在我的例子中,如果页数不是固定为2,而是可以在1到50之间,会怎么样
在其他具有用于处理JSON的sane内置工具的语言中,我只需解析JSON并递归地迭代匹配,就可以对内容执行所需的操作
示例(没有提高可读性的安全预防措施):
JS:
Python 3:
import json
def handleJSON(jsonStr):
jsonObj = json.loads(jsonStr)
matches = jsonObj['matches']
for page in matches:
# Recursively process every page to find what I need and process it
PHP:
所以,问题是,我如何以理智的方式在Swift中实现同样的功能,就像上面的例子一样(这肯定意味着大致相同数量的代码)?如果你知道一个第三方库可以做到这一点,我很乐意接受这样的库作为答案
更新:刚刚发现,这似乎至少和SwiftyJSON一样是解决我问题的好方法,也许更好。您可能应该尝试这两种方法一段时间,以确定哪一种更适合他们的口味和需要。首先,请记住,结构不需要在范围上是全局的。您可以在函数中“临时”定义一个结构,只是为了帮助您深入JSON并提取值。因此,代码的整体架构不会受到任何影响 其次,您自己的示例JSON并不像您所说的那么疯狂;疯狂的是你自己的结构。首先,使用两个相同的结构。拥有结构页1和结构页2没有任何意义。第二,你的比赛也没有目的。这不是您的JSON。在MatchesData中,
matches
属性应该只是一个类型为[String:[Person]]]
的字典(您没有Person类型,但这些东西似乎就是这样的)
如果你声称字典应该是JSON中的一个数组,好吧,我同意。如果密钥总是被命名为“page1”
,“page2”
,那么JSON在这方面是愚蠢的。但随后您可以将[String:[Person]]
转换为一个按字符串的“page”
键末尾的数字排序的Person数组。然后你可以把它映射成一个感兴趣的数组,如果你想扔掉剩下的个人信息
换句话说:如何解析到达的数据和如何维护感兴趣的数据是两个完全不同的问题。解析数据只是让自己离开JSON世界,进入对象世界;然后把它转换成对你有用的形式,保留你需要的,忽略你不需要的
下面是一个你可能会做的事情的例子:
let data = s.data(using: .utf8)!
struct Result : Codable {
let matches:[String:[Person]]
}
struct Person : Codable {
let name:String
let surname:String
let interests:[Interests]
}
struct Interests : Codable {
let id:Int
let text:String
}
let result = try! JSONDecoder().decode(Result.self, from: data)
好了,现在我们已经分析了该死的数据。但我们讨厌它的结构。所以改变它
// okay, but dictionary is silly: flatten to an array
let persons1 = Array(result.matches)
func pageNameToNumber(_ s:String) -> Int {
let num = s.dropFirst(4)
return Int(num)!
}
let persons2 = persons1.map {(key:pageNameToNumber($0.key), value:$0.value)}
let persons3 = persons2.sorted {$0.key < $1.key}
let persons4 = persons3.map { $0.value }
现在你说你根本不关心这个人的事情,你想要的只是兴趣列表:
// okay but all I care about are the interests
let interests = persons5.map {$0.interests}
(然后你也可以平摊利息,或者其他任何东西。)
现在,如果所有这些都在一个方法中完成,那么只有兴趣结构需要是公共的;其他一切都只是获取提取值的工具,可以是方法内部的私有工具。仅公开应用程序维护数据时真正需要/想要的结构。不过,总的教训是:只需将该死的JSON解析为对象:现在您身处Swift对象世界,并且可以以任何最终对您有用的方式重新解析这些对象。如果您正在寻找一个实现这一点的外部库,您可以尝试,这样您就可以获得如下所有兴趣:
{
"matches": {
"page1": [{
"name": "John",
"surname": "Doe",
"interests": [{
"id": 13,
"text": "basketball"
},
{
"id": 37,
"text": "competitive knitting"
},
{
"id": 127,
"text": "romcoms"
}
]
}],
"page2": [{
"name": "Dwayne",
"surname": "Johnson",
"interests": [{
"id": 42,
"text": "sci-fi"
},
{
"id": 255,
"text": "round numbers"
}
]
}]
}
}
struct MatchesData: Codable {
let matches: Matches
}
struct Matches: Codable {
let page1: Page1
let page2: Page2
}
struct Page1: Codable {
let interests: [Interest]
}
struct Page2: Codable {
let interests: [Interest]
}
struct Interest: Codable {
let id: Int
let text: String
}
func handleJSON(_ response: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MatchesData.self, from: response)
// Only here I can start actually working with the data API sent me, it's in decodedData
...
} catch {
// Handle the errors somehow
return
}
}
let json = JSON(parseJSON: jsonString)
let matchesObject = json["matches"]
// I assume the total number of pages is stored in numberOfPages
let interests = (0..<2).flatMap {
matchesObject["page\($0 + 1)"].arrayValue.flatMap {
$0["interests"].arrayValue.map {
// using the same Interest struct as in your question
Interest(id: $0["id"].intValue, text: $0["text"].stringValue)
}
}
}
let json=json(parseJSON:jsonString)
让matchesObject=json[“matches”]
//我假设总页数存储在numberOfPages中
let interests=(0..Codable
旨在鼓励人们远离“设计拙劣的API”:-)在您的MatchesData
结构中,使用字典而不是带有两个字段的结构Matches
怎么样[String:Interest]
@SimonMoshenko这样更好,但我真的不知道如何跳过中间结构。我想你想说的一件事是OP不应该只关注“获取所有兴趣”这样的“简单任务”,他们应该首先解析所有数据(谁知道他们什么时候会派上用场呢?)然后把所有的兴趣放在平面图上。如果他们这样做,那么这些“准备”就不会显得“太多”.我对你的答案解释正确了吗?这是一个很好的建议,我很感激!我会深入探讨,因为我还没有弄清楚如何在Swift中区分这两个问题。无论如何,非常感谢!那很好,并且不会以任何方式破坏讨论。你的例子只是一个例子,我的也是。我只是向你展示如何思考about解决这个问题,让您对一端的JSON和另一端的最终挖掘数据感觉更好。您可以以任何方式修改我的特定转换!它们只是假设性的响应。此外,JSON本身来自另一种语言的旧项目,我只是