Swift parsing JSON, nested array - json

{
"def": [
{
"sseq": [
[
[
"sense",
{
"sn": "1",
"dt": [
[
"text",
"{bc}a set of the equipment used in a particular activity {bc}{sx|gear||} "
],
[
"vis",
[
{
"t": "fishing {wi}tackle{/wi}"
}
]
]
]
}
]
]
]
}
]
}
jsonFormat
I am having trouble parsing this JSON, with the nested arrays. I am trying to get to the definition under "text" in "dt". The current output is something like this, while I am just trying to get to the definition:
["text", "{bc}to emit puffs (as of breath or steam)"]
["text", "{bc}to make empty threats {bc}{sx|bluster||}"]
["text", "{bc}to react or behave indignantly"]
for result in jsonArray {
if let def = result["def"] as? JsonArray {
for defItem in def {
//print(defItem)
if let sseq = defItem["sseq"] as? [Any] {
for _1 in sseq {
if let _1arr = _1 as? [Any] {
for _2 in _1arr {
if let _2arr = _2 as? [Any] {
for _3 in _2arr {
if let res = _3 as? JsonDict {
if let definitions = res["dt"] as? [[String]] {
print(definitions[0])
}

As #Robert pointed out, this JSON is in a bad place. As a fun thought exercise here's what you can do.
Formatted json:
{
"def":[
{
"sseq":[
[
[
"sense",
{
"sn":"1",
"dt":[
[
"text",
"{bc}a set of the equipment used in a particular activity {bc}{sx|gear||} "
],
[
"vis",
[
{
"t":"fishing {wi}tackle{/wi}"
}
]
]
]
}
]
]
]
}
]
}
Meet Swift Codable:
let woah = try? newJSONDecoder().decode(Woah.self, from: jsonData)
// MARK: - Woah
struct Woah {
let def: [Def]
}
// MARK: - Def
struct Def {
let sseq: [[[SseqElement]]]
}
enum SseqElement {
case sseqClass(SseqClass)
case string(String)
}
// MARK: - SseqClass
struct SseqClass {
let sn: String
let dt: [[DtUnion]]
}
enum DtUnion {
case dtClassArray([DtClass])
case string(String)
}
// MARK: - DtClass
struct DtClass {
let t: String
}

Related

Decoding MultiType Array and Unknown Length Array within JSON using Swift

I've seen questions like this asked before on Stack Overflow, however none match this complexity. When I attempt to apply the same principles I've seen in similar StackOverflow questions, I get stuck. I'm new to using JSONs in Swift, and certainly understand how to decode fairly complex JSONs. However, this is just beyond my reach.
{
"def": [
{
"sseq": [
[
[
"pseq",
[
[
"sense",
{
"sn": "1 a (1)",
"dt": [
[
"text",
"{bc}an extremely young child"
]
],
"sdsense": {
"sd": "especially",
"dt": [
[
"text",
"{bc}{sx|infant||}"
]
]
}
}
],
[
"sense",
{
"sn": "(2)",
"dt": [
[
"text",
"{bc}an extremely young animal"
]
]
}
]
]
],
[
"sense",
{
"sn": "b",
"dt": [
[
"text",
"{bc}the youngest of a group "
],
[
"vis",
[
{
"t": "He is the {wi}baby{/wi} of the family."
}
]
]
]
}
]
],
[
[
"sense",
{
"sn": "2 a",
"dt": [
[
"text",
"{bc}one that is like a baby (as in behavior) "
],
[
"vis",
[
{
"t": "When it comes to getting shots, I'm a real {wi}baby{/wi}."
}
]
]
]
}
],
[
"sense",
{
"sn": "b",
"dt": [
[
"text",
"{bc}something that is one's special responsibility, achievement, or interest "
],
[
"vis",
[
{
"t": "The project was his {wi}baby{/wi}."
}
]
]
]
}
]
],
[
[
"sen",
{
"sn": "3",
"sls": [
"slang"
]
}
],
[
"sense",
{
"sn": "a",
"dt": [
[
"text",
"{bc}{sx|girl||}, {sx|woman||} "
],
[
"uns",
[
[
[
"text",
"often used in address"
]
]
]
]
]
}
],
[
"sense",
{
"sn": "b",
"dt": [
[
"text",
"{bc}{sx|boy||}, {sx|man||} "
],
[
"uns",
[
[
[
"text",
"often used in address "
],
[
"vis",
[
{
"t": "Hey {wi}baby{/wi}, nice car!"
}
]
]
]
]
]
]
}
]
],
[
[
"sense",
{
"sn": "4",
"dt": [
[
"text",
"{bc}{sx|person||}, {sx|thing||} "
],
[
"vis",
[
{
"t": "is one tough {wi}baby{/wi}"
}
]
]
]
}
]
]
]
}
]
}
To clarify, I only want the object that contains the "sn" and "dt" properties and looks like the following:
{
"sn":"b",
"dt":[
[
"text",
"{bc}the youngest of a group "
],
[
"vis",
[
{
"t":"He is the {wi}baby{\/wi} of the family."
}
]
]
]
}
What makes this so complicated is that:
The first "sseq" (for example) contains a 5D array that combines strings with more arrays within the same array, which happens at multiple levels.
Sometimes, it is unknown how many arrays I have to decode to get to that level.
Any help on this is appreciated!
The below function will generate an array with all values that has any of the given keys
func extract(_ keys: [String], from value: Any, to result: inout [String, Any)]) {
if let dictionary = value as? [String: Any] {
var anyFound = false
for key in keys {
if let value = dictionary[key] {
result.append((key, value))
anyFound = true
}
}
if !anyFound {
for item in dictionary {
extract(keys, from: item.value, to: &result)
}
}
} else if let array = value as? [Any] {
for item in array {
extract(keys, from: item, to: &result)
}
}
}
and you can use it like this
var array = [(String:Any)]()
do {
if let result = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
extract(["dt", "sn"], from: result, to: &array)
}
} catch {
print(error)
}
Then it is only a matter of decoding/converting the content of the array.
For completeness-sake, another approach to make this work is to encapsulate the types of values that a JSON could hold as an enum with associated values:
enum Json {
case obj([String: Json])
case arr([Json])
case str(String)
case int(Int)
case num(Decimal)
case bool(Bool)
case null
}
Then you could make it Decodable by trying to decode either as a dictionary, failing that - as an array, and finally, failing that, as a primitive value:
extension Json: Decodable {
// Need this to decode arbitrary keys
struct DynamicKeys: CodingKey {
var stringValue: String
var intValue: Int? = nil
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
// try to decode as a dictionary
if let container = try? decoder.container(keyedBy: DynamicKeys.self) {
var dict: [String: Json] = [:]
for key in container.allKeys {
let jsonValue = try container.decode(Json.self, forKey: key)
dict[key.stringValue] = jsonValue
}
self = .obj(dict)
// try to decode as an array
} else if var container = try? decoder.unkeyedContainer() {
var array: [Json] = []
while !container.isAtEnd {
let jsonValue = try container.decode(Json.self)
array.append(jsonValue)
}
self = .arr(array)
// try to decode a primitive
} else if let container = try? decoder.singleValueContainer() {
if let int = try? container.decode(Int.self) {
self = .int(int)
} else if let decimal = try? container.decode(Decimal.self) {
self = .num(decimal)
} else if let str = try? container.decode(String.self) {
self = .str(str)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if container.decodeNil() {
self = .null
} else {
fatalError("this shouldn't have happened")
}
} else {
fatalError("this shouldn't have happened")
}
}
}
You could provide some convenience subscripts to access an array or a dictionary:
extension Json {
subscript(index: Int) -> Json? {
if case .array(let array) { return array[index] }
else { return nil }
}
subscript(index: String) -> Json? {
if case .obj(let dict) { return dict[index] }
else { return nil }
}
}
Then you could decode it normally, and you'd get an enum of possibly more enums as a result:
let json = try JSONDecoder().decode(Json.self, jsonData)
let innerJson = json["foo"]?[0]?["bar"]

How to serialize JSON string to multidimensional NSDictionary

"[{\"person\":\"person1\",\"data\":{\"age\":\"10\",\"name\":\"John\"}},
{\"person\":\"person2\",\"data\":{\"age\":\"20\",\"name\":\"Jonathan\"}},
{\"person\":\"person3\",\"data\":{\"age\":\"30\",\"name\":\"Joe\"}}]"
Note that the value "data" is also a dictionary.
I have a JSON string like above and am trying to serialize like:
if let dataFromString = conf.data(using: .utf8, allowLossyConversion: false) {
let json = try JSON(data: dataFromString)
configuration = json.dictionary ?? [:]
}
However configuration is always an empty dictionary.
You need to parse the JSON you've as an array of dictionaries of type [[String: Any]]. The better modern approach is to use Decodable model to decode the JSON.
let string = """
[
{
"person": "person1",
"data": {
"age": "10",
"name": "John"
}
},
{
"person": "person2",
"data": {
"age": "20",
"name": "Jonathan"
}
},
{
"person": "person3",
"data": {
"age": "30",
"name": "Joe"
}
}
]
"""
let data = Data(string.utf8)
struct Person: Decodable {
let person: String
let data: PersonData
}
struct PersonData: Decodable {
let age, name: String
}
do {
let people = try JSONDecoder().decode([Person].self, from: data)
print(people)
} catch { print(error) }
For the JSON String,
let conf = "[{\"person\":\"person1\",\"data\":{\"age\":\"10\",\"name\":\"John\"}},{\"person\":\"person2\",\"data\":{\"age\":\"20\",\"name\":\"Jonathan\"}},{\"person\":\"person3\",\"data\":{\"age\":\"30\",\"name\":\"Joe\"}}]"
use JSONSerialization's jsonObject(with:options:) method to get the expected response.
if let conf = str.data(using: .utf8 ) {
do {
let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [[String:Any]]
print(dict)
} catch {
print(error)
}
}

How to parse JSON in Swift with dynamic filename using Codable

I am trying to parse the following JSON into a class, but don't know how to approach this particular case.
Here is the api: https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&indexpageids&titles=bird
I am trying to get the title and extract, but in order to do so, it requires that I go through the unique pageid. How would I do this using the Codable protocol?
{
"batchcomplete": "",
"query": {
"normalized": [
{
"from": "bird",
"to": "Bird"
}
],
"pageids": [
"3410"
],
"pages": {
"3410": {
"pageid": 3410,
"ns": 0,
"title": "Bird",
"extract": "..."
}
}
}
}
My suggestion is to write a custom initializer:
Decode pages as [String:Page] dictionary and map the inner dictionaries according to the values in pageids
let jsonString = """
{
"batchcomplete": "",
"query": {
"normalized": [
{
"from": "bird",
"to": "Bird"
}
],
"pageids": [
"3410"
],
"pages": {
"3410": {
"pageid": 3410,
"ns": 0,
"title": "Bird",
"extract": "..."
}
}
}
}
"""
struct Root : Decodable {
let query : Query
}
struct Query : Decodable {
let pageids : [String]
let pages : [Page]
private enum CodingKeys : String, CodingKey { case pageids, pages }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pageids = try container.decode([String].self, forKey: .pageids)
let pagesData = try container.decode([String:Page].self, forKey: .pages)
self.pages = self.pageids.compactMap{ pagesData[$0] }
}
}
struct Page : Decodable {
let pageid, ns : Int
let title, extract : String
}
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch {
print(error)
}

Order of JSON node changes while making POST request Swift

I was trying to post the following jSON body
request JSON :
let parameters = [
"createTransactionRequest": [
"merchantAuthentication": [
"name": "xxxxxxxx",
"transactionKey": "xxxxxxxxx"
],
"refId": "123456",
"transactionRequest": [
"transactionType": "authCaptureTransaction",
"amount": "5",
"payment": [
"opaqueData": [
"dataDescriptor": desc!,
"dataValue": tocken!
]
]
]
]
]
When I am trying to print(parameters) the order of node changes it looks like
["createTransactionRequest":
["refId": "123456",
"transactionRequest":
["payment": ["opaqueData": ["dataDescriptor": "COMMON.ACCEPT.INAPP.PAYMENT", "dataValue": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="]],
"transactionType": "authCaptureTransaction",
"amount": "5"],
"merchantAuthentication": ["name": "xxxxxxx", "transactionKey":
"6gvE46G5seZt563w"]
]
]
I am getting response like
{ messages = {
message = (
{
code = E00003;
text = "The element 'createTransactionRequest' in namespace
'AnetApi/xml/v1/schema/AnetApiSchema.xsd' has invalid child element
'refId' in namespace 'AnetApi/xml/v1/schema/AnetApiSchema.xsd'. List of
possible elements expected: 'merchantAuthentication' in namespace
'AnetApi/xml/v1/schema/AnetApiSchema.xsd'.";
}
);
resultCode = Error;
};
}
This is really annoying. anyones help will be highly grateful.
You need to change your data structure as follow:
struct Transaction: Codable {
let createTransactionRequest: CreateTransactionRequest
}
struct CreateTransactionRequest: Codable {
let merchantAuthentication: MerchantAuthentication
let refId: String
let transactionRequest: TransactionRequest
}
struct MerchantAuthentication: Codable {
let name: String
let transactionKey: String
}
struct TransactionRequest: Codable {
let transactionType: String
let amount: String
let payment: Payment
}
struct Payment: Codable {
let opaqueData: OpaqueData
}
struct OpaqueData: Codable {
let dataDescriptor: String
let dataValue: String
}
Testing
let json = """
{ "createTransactionRequest":
{ "merchantAuthentication":
{ "name": "YOUR_API_LOGIN_ID",
"transactionKey": "YOUR_TRANSACTION_KEY"
},
"refId": "123456",
"transactionRequest":
{ "transactionType": "authCaptureTransaction",
"amount": "5",
"payment":
{ "opaqueData":
{ "dataDescriptor": "COMMON.ACCEPT.INAPP.PAYMENT",
"dataValue": "PAYMENT_NONCE_GOES_HERE"
}
}
}
}
}
"""
let jsonData = Data(json.utf8)
do {
let transaction = try JSONDecoder().decode(Transaction.self, from: jsonData)
print(transaction)
// encoding
let encodedData = try JSONEncoder().encode(transaction)
print(String(data: encodedData, encoding: .utf8)!)
} catch {
print(error)
}
{"createTransactionRequest":{"merchantAuthentication":{"name":"YOUR_API_LOGIN_ID","transactionKey":"YOUR_TRANSACTION_KEY"},"refId":"123456","transactionRequest":{"transactionType":"authCaptureTransaction","amount":"5","payment":{"opaqueData":{"dataValue":"PAYMENT_NONCE_GOES_HERE","dataDescriptor":"COMMON.ACCEPT.INAPP.PAYMENT"}}}}}

Get JSON element swift

I am working receiving the following JSON file in Swift and I cant figure out how get the details elements in the JSON
[
{
"id": 143369,
"history": "jd2",
"details": [
{
"name": "Su 1",
"color": "#ffffff"
},
{
"name": "Stu 1",
"color": "#ffffff"
}
]
},
{
"id": 143369,
"history": "musa 2",
"details": [
{
"name": "Stu 1",
"color": "#ffffff"
},
{
"name": "Stu 2",
"color": "#ffffff"
}
]
}
]
I have created this class with which I am able to retrieve id and history but not the details. How do I include the details with the id and history?
public class students {
let id: Int32
let history: String?
init(id:Int32, history:String) {
self.id = id
self.history = name
}
}
Below is my web service code.
var dataArray = [students]()
Alamofire.request(.GET, url)
.responseJSON { response in
if let value: AnyObject = response.result.value {
let json = JSON(value)
if let items = json.array {
for item in items {
self.dataArray.append(students(
id: item["id"].int32!,
history: item["history"].string!))
let cItems = item["details"].array
for citem in citems {
//here
}
}
}
}
}
your student model should be like this.
let id: Int32
let history: String?
let details: Array<[String:AnyObject]>
init(id:Int32, history:String,details:Array<[String:AnyObject]>) {
self.id = id
self.history = name
self.details= details //need a cast here!
}
here is a simple parser for i used for a project to cast your Array<[String:AnyObject]> as you
func collection(data:[[String:AnyObject]]) -> [yourModel] {
var objectArray: [yourModel] = []
for d in data {
let obj = yourModel(data: d as [String: AnyObject]) // i created a initializer for this object here on my project.
objectArray.append(obj)
}
return objectArray
}
hope gives an idea!