Unable to extract data properly from JSON in swift - ios

I have a this kind of json object in my response after parsing json string to object
[
"requestId": 1,
"response": {
code = SUCCESS;
},
"messageId": ACTION_COMPLETE
]
I am trying to extract requestId using
responseMsg["requestId"] as! Int
I am getting this error
Could not cast value of type 'NSTaggedPointerString' (0x21877a910) to
'NSNumber' (0x218788588).
I tried it changing to Int(responseMsg["requestId"] as! String)!
This thing is working for positive numbers but not for negative numbers probably bcz when requestId = -2 it throws me an error
Could not cast value of type '__NSCFNumber' (0x21877a000) to
'NSString' (0x218788290).
I tried with different other solution too but did not work.

For parsing the JSON data, its better use Codable instead of manually parsing everything.
For JSON format,
{
"requestId": 1,
"response": {
"code":"SUCCESS"
},
"messageId": "ACTION_COMPLETE"
}
Create Models like,
struct Root: Decodable {
let requestId: String?
let messageId: String
let response: Response
enum CodingKeys: String, CodingKey {
case requestId, messageId, response
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let id = try? values.decode(Int.self, forKey: .requestId) {
requestId = String(id)
} else if let id = try? values.decode(String.self, forKey: .requestId) {
requestId = id
} else {
requestId = nil
}
messageId = try values.decode(String.self, forKey: .messageId)
response = try values.decode(Response.self, forKey: .response)
}
}
Now, parse the JSON data using,
do {
let root = try JSONDecoder().decode(Root.self, from: data)
print(root.requestId) //access requestId like this....
} catch {
print(error)
}

Try
Int(String(describing: responseMsg["requestId"]))!
This ensures any data is converted to string first and then to int

This error message
Could not cast value of type 'NSTaggedPointerString' (0x21877a910) to 'NSNumber' (0x218788588).
tells us that the JSON request id is being parsed as a string. NSTaggedPointerString is a special internal type used by the ObjC runtime to represent strings.
Try this:
let requestId = responseMsg["requestId"] as! String
print("request id: \(requestId)") // Prints a string
Note, it may print something that looks like a number, but it isn't one.
The JSON you are parsing probably looks like
{
"requestId": "1",
"response": {
"code" = "SUCCESS"
},
"messageId": "ACTION_COMPLETE"
}
Note the 1 in quotes.

Swift 5
String interpolation is what worked for me! (casting it first to String didn't work as I had other values for which the json decoder actually did its job and cast them directly to a number)
if let responseMsg_any = responseMsg["requestId"],
let responseMsg_int = Int("\(responseMsg_any)") {
//..
}
Warning:
This solution allows any Type to become a String and be checked for a Int value. Only use this if you're not concerned about the value's Type prior to interpolation.

Related

How Fix Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 52."

How to Convert this one. "{\n ID = \"d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b\";\n Name = Headquarters;\n}"; To this format {
"ID": "d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b",
"Name": "Headquarters"
}
if let jsonString = text as? String {
let objectData = jsonString.data(using: String.Encoding.utf8)
do {
let json = try JSONSerialization.jsonObject(with: objectData!, options: .allowFragments) as! [String:Any] //try JSONSerialization.jsonObject(with: objectData!, options: JSONSerialization.ReadingOptions.mutableContainers)
print(String(describing: json))
return json
} catch {
// Handle error
print(error)
}
}
Blockquote
First of all and already mentioned the string format is clearly not JSON.
It's the string format which is returned when calling the description property of a Foundation collection type (NSArray / NSDictionary).
For example a print statement calls description and this format appears also in output of Terminal.app.
However there is a solution: This string format is called openStep (an OpenStep / NeXt legacy format) and is available in PropertyListSerialization
This code reads the format:
let string = "{\n ID = \"d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b\";\n Name = Headquarters;\n}"
let data = Data(string.utf8)
do {
let dictionary = try PropertyListSerialization.propertyList(from: data, format: nil)
print(dictionary)
} catch { print(error) }
Note:
I'm pretty sure that the original data format is not openStep and somewhere you created the string unnecessarily with the String(describing initializer like in the question.
your json format is incorrect. If you try it with jsonformatter it will throw this error:
so first you need to replace ; with ,. The second is that Strings should be wrapped in double quotes, replace Name = Headquarters with Name = "Headquarters".
This is the right form
{\n ID = \"d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b\",
\n Name = "Headquarters"\n}

How can I decode this json with Alamofire? [duplicate]

This question already has answers here:
How to parse a JSON file in swift?
(18 answers)
Closed 3 years ago.
I want to print just value for key "User_number"
[
{
"User_fullname": null,
"User_sheba": null,
"User_modifiedAT": "2019-01-31T18:37:02.716Z",
"_id": "5c53404e91fc822c80e75d23",
"User_number": "9385969339",
"User_code": "45VPMND"
}
]
I suppose this is some JSON in Data format
let data = Data("""
[ { "User_fullname": null, "User_sheba": null, "User_modifiedAT": "2019-01-31T18:37:02.716Z", "_id": "5c53404e91fc822c80e75d23", "User_number": "9385969339", "User_code": "45VPMND" } ]
""".utf8)
One way is using SwiftyJSON library, but, this is something what I don't suggest since you can use Codable.
So, first you need custom struct conforming to Decodable (note that these CodingKeys are here to change key of object inside json to name of property of your struct)
struct User: Decodable {
let fullname, sheba: String? // these properties can be `nil`
let modifiedAt, id, number, code: String // note that all your properties are actually `String` or `String?`
enum CodingKeys: String, CodingKey {
case fullname = "User_fullname"
case sheba = "User_sheba"
case modifiedAt = "User_modifiedAT"
case id = "_id"
case number = "User_number"
case code = "User_code"
}
}
then decode your json using JSONDecoder
do {
let users = try JSONDecoder().decode([User].self, from: data)
} catch { print(error) }
So, now you have Data decoded as array of your custom model. So if you want to, you can just get certain User and its number property
let user = users[0]
let number = user.number
The following code takes takes in Data and saves "User_number" as an Int
if let json = try? JSONSerialization.jsonObject(with: Data!, options: []) as! NSDictionary {
let User_number= json["User_number"] as! Int
}

What's the appropriate way to access an enum from Swift Decodables?

I have a really weird case where I took JSON and thought I would be able to substring all the way to the data I want to access, however, it's not working as expected.
Using QuickType, I was able to convert this JSON: https://kidsuper.fodalabs.com/wp-json/wp/v2/art
To the below.
When trying to access, it seems like I should be able to do .acf.gallery.id however once I get to acf.gallery, it says .id does not exist. This is strange but here's what it returns when I try
let temp = imageArray[indexPath.row].acf.gallery.id
Value of type 'GalleryUnion?' has no member 'id'
Just for fun I tried this one and had no luck as well:
let temp = imageArray[indexPath.row].acf.GalleryUnion.galleryElementArray
Error
: Value of type 'Acf' has no member 'GalleryUnion'
The return when I print .acf.gallery starts like this:
Acf(company: "Season #3",
gallery: Optional(weddingszn.GalleryUnion.galleryElementArray([weddingszn.GalleryElement(
id: 135, galleryID: 135, title: "1-791x1024",
filename: "1-791x1024.jpg", url: "https://kidsuper.fodalabs.com/wp-content/up
Full code is below for what I'm trying to parse. Any ideas?
struct Acf: Codable {
let company: String
let gallery: GalleryUnion?
let tagline: String
let featuredImg: Bool?
enum CodingKeys: String, CodingKey {
case company, gallery, tagline
case featuredImg = "featured_img"
}
}
enum GalleryUnion: Codable {
case bool(Bool)
case galleryElementArray([GalleryElement])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode([GalleryElement].self) {
self = .galleryElementArray(x)
return
}
throw DecodingError.typeMismatch(GalleryUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for GalleryUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .galleryElementArray(let x):
try container.encode(x)
}
}
}
struct GalleryElement: Codable {
let id, galleryID: Int
let title, filename: String
let url: String
let alt, author, description, caption: String
let name, date, modified: String
let mimeType: MIMEType
let type: TypeEnum
let icon: String
let width, height: Int
let sizes: Sizes
enum CodingKeys: String, CodingKey {
case id = "ID"
case galleryID = "id"
case title, filename, url, alt, author, description, caption, name, date, modified
case mimeType = "mime_type"
case type, icon, width, height, sizes
}
}
You have to use if case, guard case or switch case to unpack the enum before you drill down into the array.
if case .galleryElementArray(let x) = imageArray[indexPath.row].acf.gallery {
print(x.first!.id)
}
however once I get to acf.gallery, it says .id does not exist. This is strange
No it isn't. According to your code, .gallery should be a GalleryUnion. Well, a GalleryUnion has no .id. It has no properties at all. A GalleryElement has an .id, but a GalleryUnion is not a GalleryElement. So I don't see where your surprise comes from; there is no surprise here.
A GalleryUnion has cases. You need to check which case this is. If it's . galleryElementArray you need to extract the associated value. Even then you won't have any .id, because what you will now have is still not a GalleryElement; it's an array of GalleryElements.
You could make this a lot easier on yourself by defining GalleryUnion with an extra calculated property that fetches the associated value for you:
enum GalleryUnion : Codable {
case bool(Bool)
case galleryElementArray([GalleryElement])
var galleryElements : [GalleryElement]? {
switch self {
case .bool : return nil
case let .galleryElementArray(arr): return arr
}
}
// ... and so on
}
That would allow you, at least, to say:
act.gallery.galleryElements?.map {$0.id}
...or whatever it is you have in mind.
So, GalleryUnion can one of two things. It can either both .bool(_) or galleryElementArray(_). When you want access the actual underlying value, you need to determine which state it's in.
To do this in Swift, you can use a switch statement. You can then use it to gain access to the internally contained values. Maybe something similar to:
if let gallery = acf.gallery {
switch gallery {
case .galleryElementArray(let values):
values.forEach {
print($0.id)
}
case .bool(_):
break
}
}
You might like to have a read of Enumerations, look for the "Associated Values" sections

Swifty Json parsing

I am using SwiftyJson library for parsing my following json
{
"data": {
"id": "12345",
"messages": {
"message": "{\"data\":{\"msg\":\"HelloMsg\"}}"
}
}
}
I tried to use following code to get msg parameter
let json = JSON(data)
let msg = JSON(json["data"]["messages"]["message"])
msg["data"]["msg"].stringValue
However, I could not get the value of msg parameter. What shall I do to get HelloMsg?
The content of the "message" field is not parsed JSON, it's a JSON string.
Use SwiftyJSON's JSON(parseJSON:) initializer to accept a string as input and parse it as JSON:
let messages = json["data"]["messages"]["message"].stringValue
let innerJSON = JSON(parseJSON: messages)
let msg = innerJSON["data"]["msg"].stringValue // "HelloMsg"
The error occurs because JSON(...) is the wrong API to initialize and parse a SwiftyJSON object from a string.
You have to use this syntax:
let json = JSON(data)
let msg = JSON(parseJSON: json["data"]["messages"]["message"].stringValue)
msg["data"]["msg"].stringValue
From the documentation of init(_ object: Any):
note: this does not parse a String into JSON, instead use init(parseJSON: String)
Edit:
To test the code in a Playground
let str = """
{"data": {"id": "12345",
"messages": {
"message": "{\\"data\\":{\\"msg\\":\\"HelloMsg\\"}}"
}
}
}
"""
let data = Data(str.utf8)
let json = JSON(data)
let msg = JSON(parseJSON: json["data"]["messages"]["message"].stringValue)
msg["data"]["msg"].stringValue
The JSON as traditional literal string is
let str = "{\"data\": {\"id\": \"12345\",\"messages\": {\"message\": \"{\\\"data\\\":{\\\"msg\\\":\\\"HelloMsg\\\"}}\"}}}"
The messaage is a string. not a JSON. so SwiftyJson could not parse it. You will have to first parse that string and than get the message from that using JSONSerialization.jsonObject(with: Data, options: JSONSerialization.ReadingOptions).
You can refer to this answer to get the dictionary from that string: https://stackoverflow.com/a/30480777/7820107
Your second "message" key value is a String with a dictionary in JSON format, so you need to convert that string to JSON and access to ["data"]["msg"] then
Code
let json = JSON(data)
let msg = json["data"]["messages"]["message"]
let jsonFromString = JSON(data: msg.data(using: .utf8)!, options: JSONSerialization.ReadingOptions.allowFragments, error: nil)
debugPrint(jsonFromString["data"]["msg"])
Output
HelloMsg

SwiftyJSON keeps returning empty objects

I am new to SwiftyJSON, and I'm having some trouble with it. I can get it to return the entire JSON file as a string, but the moment I try to parse it, I keep getting empty variables back, and I'm not sure what I'm doing wrong.
This is the formatting of my JSON file:
[
{
"entryID": 1,
"from": "String",
"to": "String",
"value": "String"
},
{
...
},
...
]
And this is roughly what I want to do with it (in quite inelegant code, I do apologise, I'm new to Swift):
for entry: JSON in indexJSON.arrayValue {
var vEntryID: Int
var vFrom: String
var vTo: String
var vValue: String
for (dictKey: String, dictVal: JSON) in entry.dictionaryValue {
if(dictKey=="entryID") {vEntryID = dictVal.intValue}
if(dictKey=="from") {vFrom = dictVal.stringValue}
if(dictKey=="to") {vTo = dictVal.stringValue}
if(dictKey=="value") {vValue = dictVal.stringValue}
}
someSwiftObject[vEntryID]["from"] = vFrom
someSwiftObject[vEntryID]["to"] = vTo
someSwiftObject[vEntryID]["value"] = vValue
}
However, this block never executes at all, because indexJSON.arrayValue is always empty.
When I try to run the following, it correctly prints the complete file contents to the console:
let indexJSON = JSON(content!)
println(indexJSON.stringValue)
But when I try to go deeper, to fetch any element, it returns nothing:
if(indexJSON.arrayValue.isEmpty==true) {println("indexJSON.arrayValue is Empty")}
if(indexJSON[0].arrayValue.isEmpty==true) {println("indexJSON[0].arrayValue is Empty")}
if(indexJSON[0].dictionaryValue.isEmpty==true) {println("indexJSON[0].dictionaryValue is Empty")}
if(indexJSON[0]["entryID"]==nil) {println("indexJSON[0][\"entryID\"].stringValue is Empty")}
Output:
indexJSON.arrayValue is Empty
indexJSON[0].arrayValue is Empty
indexJSON[0].dictionaryValue is Empty
indexJSON[0]["entryID"].stringValue is Empty
I'd be grateful for any help! What am I doing wrong?
I checked SwiftyJSON source code and I think I know where the problem is.
I suppose that you are using String to initialize the JSON object like this
let s = "{\"entryID\": 1,\"from\": \"String\",\"to\": \"String\",\"value\": \"String\"}"
let j = JSON(s)
In this case the JSON object is actuall given a type "String", not Array. That's why it's not iterable and its arrayValue is empty.
To do what you want to do, you need to initialize it with an Array object:
let arr = [
[
"entryID":1,
"from":"String",
"to":"String",
"value":"String",
]
]
let j2 = JSON(arr)
Now j2 is an array JSON object and iterable.
SwiftyJSON can only be initialized with NSData and object. So if you want to initialize it with a String you need to do this:
if let data = s.dataUsingEncoding(NSUTF8StringEncoding) {
let j = JSON(data:data)
println(j)
}
first of all, make sure the format of your json string is correct. in your question, your json string is a array, just format the string like this(the content is from my code):
let jsonStr = "[{\"name\": \"hangge\", \"age\": 100, \"phones\": [{\"name\": \"公司\",\"number\": \"123456\"}, {\"name\": \"家庭\",\"number\": \"001\"}]}, {\"name\": \"big boss\",\"age\": 1,\"phones\": [{ \"name\": \"公司\",\"number\": \"111111\"}]}]"
then you can use SwityJson to get the array object, like this:
let strData = jsonStr.data(using: String.Encoding.utf8, allowLossyConversion: false)
let json = JSON(data: strData!)
for object in json.arrayValue {
let name = object["name"].string
}
Take a look at the documentation here: https://github.com/lingoer/SwiftyJSON#loop
You are iterating it incorrectly. You should be iterating over the array with a for loop like this:
for (index: String, subJson: JSON) in json {
//Do something you want
}

Resources