Swift: Convert object to Dictionary not working properly - ios

I'm trying to convert an object to NSDictionary with an extension. It works fine but if the object contains an array, then the array does not convert to NSDictionary but to Json and that is not what I want. Because I try with this Dictionary to make a request post and the body accepts [String: Any].
My code:
struct Country: Codable {
let name: String
let cities: [City]
struct City: Codable {
let name: String
}
}
extension NSDictionary {
func encode<T>(_ value: T) throws -> [String: Any]? where T: Encodable {
guard let jsonData = try? JSONEncoder().encode(value) else { return nil }
return try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [String: Any]
}
}
Using:
let country = Country(name: "France", cities: [Country.City(name: "Paris"), Country.City(name: "Marseille")])
print(try! NSDictionary().encode(country))
Output:
["name": France, "cities": <__NSArrayI 0x600002926e60>(
{
name: "Paris",
},
{
name: "Marseille",
})
]
Expected output (what I want):
["name": France, "cities": [
[
"name": Paris,
],
[
"name": Marseille,
]
]
]

The output is correct since your result is NSDictionary and the output is the string representation (aka description) of NSDictionary. <__NSArrayI 0x600002926e60> is syntactic sugar.
By the way the expected output is invalid because the city names must be in double quotes.
The extension of NSDictionary makes no sense anyway. If you want to be able to call the method from everywhere declare a struct with a static method.
This is a swiftier version of your code. No allowFragments and all errors are thrown rather than being ignored.
struct DictionaryEncoder {
static func encode<T>(_ value: T) throws -> [String: Any] where T: Encodable {
let jsonData = try JSONEncoder().encode(value)
return try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] ?? [:]
}
}
Copy the method and your structs into a Playground and run
let country = Country(name: "France", cities: [Country.City(name: "Paris"), Country.City(name: "Marseille")])
try! DictionaryEncoder.encode(country)
Don't print the result, look at the result area in the Playground, the output is exactly what you want.

You need
let res = try! JSONEncoder().encode(country)
print(res.prettyPrintedJSONString!)
extension Data {
var prettyPrintedJSONString: NSString? { /// NSString gives us a nice sanitized debugDescription
guard let object = try? JSONSerialization.jsonObject(with: self, options: []),
let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]),
let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return nil }
return prettyPrintedString
}
}
Result
{
"name" : "France",
"cities" : [
{
"name" : "Paris"
},
{
"name" : "Marseille"
}
]
}

Related

Fetch value of dictionary from JSON

I want to fetch values of string from JSON , I am able to parse JSON to Dictionary but not able to convert into array of string
FROM SERVER
{
“ALL TEXT: [
{
"text": "hello"
},
{
"text": "hi"
},
{
"text": "how r u"
}
]
}
I only want value of text to append to my string array "textsList"
var textsList : [String]()
This is what I had tried
URLSession.shared.dataTask(with: request) { (data, response, err) in
let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! Dictionary<String, Any>
let AllText = json[“ALL TEXT”] as! [Dictionary<String, Any>]
let value = JSON(AllText)
AllText.forEach { fetch in
self.textsList.append(fetch.values) // ERROR no exact matches in call to instance method append
}
for(key, object) in value{
print(value) //output: {"text": "hello" },{"text": "hi" }
print(value.string) //output:nil
}
}.resume()
I just want to convert dictionary into array , but also alternate solution to convert JSON directly into array of string will be fine.
Adjust the code:
let string = """
{
"ALL TEXT": [
{
"text": "hello"
},
{
"text": "hi"
},
{
"text": "how r u"
}
]
}
"""
guard let data = string.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? Dictionary<String, Any>,
let allText = json["ALL TEXT"] as? [Dictionary<String, Any>]
else {
DDLog("json fail")
return }
DDLog(allText) // [["text": hello], ["text": hi], ["text": how r u]]
let items: [String] = allText.compactMap { $0["text"] as? String }
var textsList = [String]()
textsList.append(contentsOf: items)
DDLog(items) //["hello", "hi", "how r u"]
DDLog(textsList) //["hello", "hi", "how r u"]
Two errors:
1. <"ALL TEXT> Missing right quote;
2. var textsList: [String]() =》var textsList = [String]();

Avoid JSONSerialization to convert Bool to NSNumber

I have JSON data that I need to convert to Dictionary so I use JSONSerialization for that purpose but when I check the created dictionary, I can see that it converts the Bool to NSNumber (for property named demo)automatically
import Foundation
struct Employee: Codable {
let employeeID: Int?
let meta: Meta?
}
struct Meta: Codable {
let demo: Bool?
}
let jsonValue = """
{
"employeeID": 1,
"meta": {
"demo": true
}
}
"""
let jsonData = jsonValue.data(using: .utf8)!
if let jsonDictionary = (try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)) as? [String: Any] {
print(jsonDictionary)
}
OUTPUT
["meta": {
demo = 1; }, "employeeID": 1]
Is there a way to avoid this Bool to NSNumber conversion or maybe convert NSNumber back to Bool using a custom logic ?
For decoding I need to convert that Dictionary to Data which I will input in JSONDecoder
If that is the case, you would use the data(withJSONObject:options:) method instead.
Below is how you would do that:
let dictionary: [String : Any] = [ "employeeID": 1,
"meta": [ "demo": true ] ]
do {
let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
let employee = try JSONDecoder().decode(Employee.self, from: data)
print(employee)
} catch {
print(error)
}
And I would think again if I really need the properties of the structs to be Optionals.

How to convert JSON String to JSON Object in swift?

I trying to generate JSON Object and convert that to JSON String and that process is Successfully placed. But my real problem rises when I try to convert JSON String to JSON Object. When I try I get nil as result.
func generateJSONObject() {
let jsonObject = createJSONObject(firstName: firstName[0], middleName: middleName[0], lastName: lastName[0], age: age[0], weight: weight[0])
print("jsonObject : \(jsonObject)")
let jsonString = jsonObject.description // convert It to JSON String
print("jsonString : \(jsonString)")
let jsonObjectFromString = convertToDictionary(text: jsonString)
print("jsonObjectFromString : \(String(describing: jsonObjectFromString))")
}
createJSONObject func
// JSON Object creation
func createJSONObject(firstName: String, middleName: String, lastName: String, age: Int, weight: Int) -> [String: Any] {
let jsonObject: [String: Any] = [
"user1": [
"first_name": firstName,
"middle_name": middleName,
"last_name": lastName,
"age": age,
"weight": weight
]
]
return jsonObject
}
convertToDictionary func
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
Logs
When I print JSON Object I get
jsonObject : ["user1": ["age": 21, "middle_name": "Lazar", "last_name": "V", "weight": 67, "first_name": "Alwin"]]
When I print JSON String I get
jsonString : ["user1": ["age": 21, "middle_name": "Lazar", "last_name": "V", "weight": 67, "first_name": "Alwin"]]
Convert JSON String to JSON Object I get below error
The data couldn’t be read because it isn’t in the correct format.
jsonObjectFromString : nil
I don't know why this happening. I want to convert JSON String to JSON Object and I want to parse the JSON.
based on discussion
import Foundation
let firstName = "Joe"
let lastName = "Doe"
let middleName = "Mc."
let age = 100
let weight = 45
let jsonObject: [String: [String:Any]] = [
"user1": [
"first_name": firstName,
"middle_name": middleName,
"last_name": lastName,
"age": age,
"weight": weight
]
]
if let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
let str = String(data: data, encoding: .utf8) {
print(str)
}
prints
{
"user1" : {
"age" : 100,
"middle_name" : "Mc.",
"last_name" : "Doe",
"weight" : 45,
"first_name" : "Joe"
}
}
Json has to be in Array or Dictionary, it can't be only string so to create a jsonstring first you need to convert to Data format and then convert to String
func generateJSONObject() {
let jsonObject = createJSONObject(firstName: "firstName", middleName: "middleName", lastName: "lastName", age: 21, weight: 82)
print("jsonObject : \(jsonObject)")
if let jsonString = convertToJsonString(json: jsonObject), let jsonObjectFromString = convertToDictionary(text: jsonString) {
print("jsonObjectFromString : \(jsonObjectFromString)")
}
}
func convertToJsonString(json: [String: Any]) -> String? {
do {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
return String(data: jsonData, encoding: .utf8)
} catch {
print(error.localizedDescription)
}
return nil
}

How to parse Array of JSON to array in Swift

I'm trying to parse JSON which is like below
[
{
"People": [
"Jack",
"Jones",
"Rock",
"Taylor",
"Rob"
]
},
{
"People": [
"Rose",
"John"
]
},
{
"People": [
"Ted"
]
}
]
to an array which results in:
[ ["Jack", "Jones", "Rock", "Taylor", "Rob"] , ["Rose", "John"], ["Ted"] ]
which is array of arrays.
I tried with code below
if let path = Bundle.main.path(forResource: "People", ofType: "json") {
let peoplesArray = try! JSONSerialization.jsonObject(
with: Data(contentsOf: URL(fileURLWithPath: path)),
options: JSONSerialization.ReadingOptions()
) as? [AnyObject]
for people in peoplesArray! {
print(people)
}
}
when I print "people" I get o/p as
{
People = (
"Jack",
"Jones",
"Rock",
"Taylor",
"Rob"
);
}
{
People = (
"Rose",
"John"
);
}
...
I'm confused how to parse when it has "People" repeated 3 times
Trying to display content in UITableView where my 1st cell has "Jack" .."Rob" and Second cell has "Rose" , "John" and third cell as "Ted"
PLease help me to understand how to achieve this
You can do this in an elegant and type safe way leveraging Swift 4 Decodable
First define a type for your people array.
struct People {
let names: [String]
}
Then make it Decodable, so that it can be initialised with a JSON.
extension People: Decodable {
private enum Key: String, CodingKey {
case names = "People"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
self.names = try container.decode([String].self, forKey: .names)
}
}
Now you can easily decode your JSON input
guard
let url = Bundle.main.url(forResource: "People", withExtension: "json"),
let data = try? Data(contentsOf: url)
else { /* Insert error handling here */ }
do {
let people = try JSONDecoder().decode([People].self, from: data)
} catch {
// I find it handy to keep track of why the decoding has failed. E.g.:
print(error)
// Insert error handling here
}
Finally to get get your linear array of names you can do
let names = people.flatMap { $0.names }
// => ["Jack", "Jones", "Rock", "Taylor", "Rob", "Rose", "John", "Ted"]
var peoplesArray:[Any] = [
[
"People": [
"Jack",
"Jones",
"Rock",
"Taylor",
"Rob"
]
],
[
"People": [
"Rose",
"John"
]
],
[
"People": [
"Ted"
]
]
]
var finalArray:[Any] = []
for peopleDict in peoplesArray {
if let dict = peopleDict as? [String: Any], let peopleArray = dict["People"] as? [String] {
finalArray.append(peopleArray)
}
}
print(finalArray)
output:
[["Jack", "Jones", "Rock", "Taylor", "Rob"], ["Rose", "John"], ["Ted"]]
In your case, it will be:
if let path = Bundle.main.path(forResource: "People", ofType: "json") {
let peoplesArray = try! JSONSerialization.jsonObject(with: Data(contentsOf: URL(fileURLWithPath: path)), options: JSONSerialization.ReadingOptions()) as? [Any]
var finalArray:[Any] = []
for peopleDict in peoplesArray {
if let dict = peopleDict as? [String: Any], let peopleArray = dict["People"] as? [String] {
finalArray.append(peopleArray)
}
}
print(finalArray)
}
let assume that the json is the encoded data
var arrayOfData : [String] = []
dispatch_async(dispatch_get_main_queue(),{
for data in json as! [Dictionary<String,AnyObject>]
{
let data1 = data["People"]
arrayOfData.append(data1!)
}
})
You can now use the arrayOfData. :D
what you have here is first an array of 3 objects. each object is a dictionary where the key is people and the value is an array of strings. when you're trying to do jsonserialization, you have to cast it down to the expected result. So you have first an array of objects, then you have a dictionary with String: Any, then you obtain an array of String
let peoplesArray = try! JSONSerialization.jsonObject(with: Data(contentsOf: URL(fileURLWithPath: path)), options: []) as? [AnyObject]
guard let peoplesObject = peoplesArray["people"] as? [[String:Any]] else { return }
for people in peoplesObject {
print("\(people)")
}
I couldn't pasted it in a comment, it is too long or something
static func photosFromJSONObject(data: Data) -> photosResult {
do {
let jsonObject: Any =
try JSONSerialization.jsonObject(with: data, options: [])
print(jsonObject)
guard let
jsonDictionary = jsonObject as? [NSObject: Any] as NSDictionary?,
let trackObject = jsonDictionary["track"] as? [String: Any],
let album = trackObject["album"] as? [String: Any],
let photosArray = album["image"] as? [[String: Any]]
else {
return .failure(lastFMError.invalidJSONData)
}
}
}
And the json was something like:
{
artist: {
name: Cher,
track: {
title: WhateverTitle,
album: {
title: AlbumWhatever,
image: {
small: "image.px",
medium: "image.2px",
large: "image.3px"}
....

Convert array to JSON string in swift

How do you convert an array to a JSON string in swift?
Basically I have a textfield with a button embedded in it.
When button is pressed, the textfield text is added unto the testArray.
Furthermore, I want to convert this array to a JSON string.
This is what I have tried:
func addButtonPressed() {
if goalsTextField.text == "" {
// Do nothing
} else {
testArray.append(goalsTextField.text)
goalsTableView.reloadData()
saveDatatoDictionary()
}
}
func saveDatatoDictionary() {
data = NSKeyedArchiver.archivedDataWithRootObject(testArray)
newData = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(), error: nil) as? NSData
string = NSString(data: newData!, encoding: NSUTF8StringEncoding)
println(string)
}
I would also like to return the JSON string using my savetoDictionart() method.
As it stands you're converting it to data, then attempting to convert the data to to an object as JSON (which fails, it's not JSON) and converting that to a string, basically you have a bunch of meaningless transformations.
As long as the array contains only JSON encodable values (string, number, dictionary, array, nil) you can just use NSJSONSerialization to do it.
Instead just do the array->data->string parts:
Swift 3/4
let array = [ "one", "two" ]
func json(from object:Any) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: String.Encoding.utf8)
}
print("\(json(from:array as Any))")
Original Answer
let array = [ "one", "two" ]
let data = NSJSONSerialization.dataWithJSONObject(array, options: nil, error: nil)
let string = NSString(data: data!, encoding: NSUTF8StringEncoding)
although you should probably not use forced unwrapping, it gives you the right starting point.
Swift 3.0 - 4.0 version
do {
//Convert to Data
let jsonData = try JSONSerialization.data(withJSONObject: dictionaryOrArray, options: JSONSerialization.WritingOptions.prettyPrinted)
//Convert back to string. Usually only do this for debugging
if let JSONString = String(data: jsonData, encoding: String.Encoding.utf8) {
print(JSONString)
}
//In production, you usually want to try and cast as the root data structure. Here we are casting as a dictionary. If the root object is an array cast as [Any].
var json = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any]
} catch {
print(error.description)
}
The JSONSerialization.WritingOptions.prettyPrinted option gives it to the eventual consumer in an easier to read format if they were to print it out in the debugger.
Reference: Apple Documentation
The JSONSerialization.ReadingOptions.mutableContainers option lets you mutate the returned array's and/or dictionaries.
Reference for all ReadingOptions: Apple Documentation
NOTE: Swift 4 has the ability to encode and decode your objects using a new protocol. Here is Apples Documentation, and a quick tutorial for a starting example.
If you're already using SwiftyJSON:
https://github.com/SwiftyJSON/SwiftyJSON
You can do this:
// this works with dictionaries too
let paramsDictionary = [
"title": "foo",
"description": "bar"
]
let paramsArray = [ "one", "two" ]
let paramsJSON = JSON(paramsArray)
let paramsString = paramsJSON.rawString(encoding: NSUTF8StringEncoding, options: nil)
SWIFT 3 UPDATE
let paramsJSON = JSON(paramsArray)
let paramsString = paramsJSON.rawString(String.Encoding.utf8, options: JSONSerialization.WritingOptions.prettyPrinted)!
JSON strings, which are good for transport, don't come up often because you can JSON encode an HTTP body. But one potential use-case for JSON stringify is Multipart Post, which AlamoFire nows supports.
How to convert array to json String in swift 2.3
var yourString : String = ""
do
{
if let postData : NSData = try NSJSONSerialization.dataWithJSONObject(yourArray, options: NSJSONWritingOptions.PrettyPrinted)
{
yourString = NSString(data: postData, encoding: NSUTF8StringEncoding)! as String
}
}
catch
{
print(error)
}
And now you can use yourSting as JSON string..
Swift 5
This generic extension will convert an array of objects to a JSON string from which it can either be:
saved to the App's Documents Directory (iOS/MacOS)
output directly to a file on the Desktop (MacOS)
.
extension JSONEncoder {
static func encode<T: Encodable>(from data: T) {
do {
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let json = try jsonEncoder.encode(data)
let jsonString = String(data: json, encoding: .utf8)
// iOS/Mac: Save to the App's documents directory
saveToDocumentDirectory(jsonString)
// Mac: Output to file on the user's Desktop
saveToDesktop(jsonString)
} catch {
print(error.localizedDescription)
}
}
static private func saveToDocumentDirectory(_ jsonString: String?) {
guard let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileURL = path.appendingPathComponent("Output.json")
do {
try jsonString?.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print(error.localizedDescription)
}
}
static private func saveToDesktop(_ jsonString: String?) {
let homeURL = FileManager.default.homeDirectoryForCurrentUser
let desktopURL = homeURL.appendingPathComponent("Desktop")
let fileURL = desktopURL.appendingPathComponent("Output.json")
do {
try jsonString?.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print(error.localizedDescription)
}
}
}
Example:
struct Person: Codable {
var name: String
var pets: [Pet]
}
struct Pet: Codable {
var type: String
}
extension Person {
static func sampleData() -> [Person] {
[
Person(name: "Adam", pets: []),
Person(name: "Jane", pets: [
Pet(type: "Cat")
]),
Person(name: "Robert", pets: [
Pet(type: "Cat"),
Pet(type: "Rabbit")
])
]
}
}
Usage:
JSONEncoder.encode(from: Person.sampleData())
Output:
This will create the following correctly formatted Output.json file:
[
{
"name" : "Adam",
"pets" : [
]
},
{
"name" : "Jane",
"pets" : [
{
"type" : "Cat"
}
]
},
{
"name" : "Robert",
"pets" : [
{
"type" : "Cat"
},
{
"type" : "Rabbit"
}
]
}
]
SWIFT 2.0
var tempJson : NSString = ""
do {
let arrJson = try NSJSONSerialization.dataWithJSONObject(arrInvitationList, options: NSJSONWritingOptions.PrettyPrinted)
let string = NSString(data: arrJson, encoding: NSUTF8StringEncoding)
tempJson = string! as NSString
}catch let error as NSError{
print(error.description)
}
NOTE:- use tempJson variable when you want to use.
extension Array where Element: Encodable {
func asArrayDictionary() throws -> [[String: Any]] {
var data: [[String: Any]] = []
for element in self {
data.append(try element.asDictionary())
}
return data
}
}
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
}
If you're using Codable protocols in your models these extensions might be helpful for getting dictionary representation (Swift 4)
Hint: To convert an NSArray containing JSON compatible objects to an NSData object containing a JSON document, use the appropriate method of NSJSONSerialization. JSONObjectWithData is not it.
Hint 2: You rarely want that data as a string; only for debugging purposes.
For Swift 4.2, that code still works fine
var mnemonic: [String] = ["abandon", "amount", "liar", "buyer"]
var myJsonString = ""
do {
let data = try JSONSerialization.data(withJSONObject:mnemonic, options: .prettyPrinted)
myJsonString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
} catch {
print(error.localizedDescription)
}
return myJsonString
Swift 5
Make sure your object confirm Codable.
Swift's default variable types like Int, String, Double and ..., all are Codable that means we can convert theme to Data and vice versa.
For example, let's convert array of Int to String Base64
let array = [1, 2, 3]
let data = try? JSONEncoder().encode(array)
nsManagedObject.array = data?.base64EncodedString()
Make sure your NSManaged variable type is String in core data schema editor and custom class if your using custom class for core data objects.
let's convert back base64 string to array:
var getArray: [Int] {
guard let array = array else { return [] }
guard let data = Data(base64Encoded: array) else { return [] }
guard let val = try? JSONDecoder().decode([Int].self, from: data) else { return [] }
return val
}
Do not convert your own object to Base64 and store as String in CoreData and vice versa because we have something that named Relation in CoreData (databases).
For Swift 3.0 you have to use this:
var postString = ""
do {
let data = try JSONSerialization.data(withJSONObject: self.arrayNParcel, options: .prettyPrinted)
let string1:String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
postString = "arrayData=\(string1)&user_id=\(userId)&markupSrcReport=\(markup)"
} catch {
print(error.localizedDescription)
}
request.httpBody = postString.data(using: .utf8)
100% working TESTED
You can try this.
func convertToJSONString(value: AnyObject) -> String? {
if JSONSerialization.isValidJSONObject(value) {
do{
let data = try JSONSerialization.data(withJSONObject: value, options: [])
if let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
return string as String
}
}catch{
}
}
return nil
}

Resources