Create JSON String from Array? - swift

This might be an easy question, but how can I create a simple JSON String from an existing Array?
In the Documentation the only thing I found is:
class func JSONObjectWithData(_ data: NSData,
options opt: NSJSONReadingOptions,
error error: NSErrorPointer) -> AnyObject?
But I only want to create a simple JSON String from an existing Array.
With the existing Swift JSON Extensions I am only able to parse existing JSON Strings, and could not create a new String.
I could manually create the String, but there might be a better solution.
Any help would be greatly appreciated.

If you have a NSArray object, you could create your JSON String by using something like:
func JSONStringify(value: AnyObject, prettyPrinted: Bool = false) -> String {
var options = prettyPrinted ? NSJSONWritingOptions.PrettyPrinted : nil
if NSJSONSerialization.isValidJSONObject(value) {
if let data = NSJSONSerialization.dataWithJSONObject(value, options: options, error: nil) {
if let string = NSString(data: data, encoding: NSUTF8StringEncoding) {
return string
}
}
}
return ""
}
I found here the details for this function.
SWIFT 3
func JSON2String(jsonObj: AnyObject, prettyPrinted: Bool = false) -> String {
let options = prettyPrinted ? JSONSerialization.WritingOptions.prettyPrinted : []
if JSONSerialization.isValidJSONObject(jsonObj) {
if let data = try? JSONSerialization.data(withJSONObject: jsonObj, options: options) {
if let string = String(data: data, encoding: Task.StringEncoding) {
return string
}
}
}
return ""
}

Let's say you have an array of Strings called array. You would do this:
let array = [ "String1", "String2" ]
var error:NSError?
let data = NSJSONSerialization.dataWithJSONObject(array, options: NSJSONWritingOptions.allZeros, error: &error)
let str = NSString(data:data!, encoding:NSUTF8StringEncoding)
println("\(str!)")

Related

Swift Invalid type in JSON write error using JSONSerialization.data

I have an array with elements of a custom type. Here is the type :
public class RequestElemDataBody: Codable {
public var name: String
public var value: String
public init(name: String, value: String) {
self.name = name
self.value = value
}
}
This is how I declare my array :
var elementsInForm = [RequestElemDataBody]()
I use a function to convert this array to Data then to String :
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)
}
When executing I get this error message :
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (Data.RequestElemDataBody)'
I don't know what is wrong with my custom type since it is Codable.
How can I parse my array with my function without it throwing an error ?
You should use JSONEncoder when serializing Codable.
The example from the documentation page:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/
Problem with your approach is that you try to encode the whole array. But your codable object is not an array at the moment.
Try passing single element to your method and return the string to work with it outside;
func json(from element: RequestElemDataBody) -> String {
...
}
As mentioned here the top level object can be Array or Dictionary and all objects can be instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
. In your case they are instances of an custom object.
So you can try converting the objects to dictionary first and then use JSONSerialization
And to convert to dictionary you can create an extension on Encodable
extension Encodable {
var dict : [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else { return nil }
return json
}
}
and while calling function you can use ArrayName.compactMap({$0.dict})
Also you can use ObjectMapper library.
Reference from: How to convert a Swift object to a dictionary

Adding nested dictionary causes JSONSerialization to return nil

I have the following structure that is used to pass JSON data to a REST endpoint. Originally the object contained only one level of key-value pairs. In that scenario, serializing to a JSON object worked properly.
Now I need to add a dictionary as a parameter, which should create a nested dictionary in the resulting JSON. However, adding the nested dictionary causes JSONSerialization to return nil.
Code:
struct ServicePayload:Codable {
private var name:String
private var type:String
private var deviceId:String
private var clientType:String
private var appInstanceId:String
private var version:String
private var addParams:[String:String] // causes failure
init(name:String, type:String, version:String, params:[String:String]) {
self.name = name
self.type = type
self.deviceId = Constants.Device.identifier!
self.version = version
self.clientType = "1"
self.appInstanceId = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
self.addParams = params
}
// json mapper
private enum CodingKeys:String, CodingKey {
case name = "name"
case type = "contentTypes"
case deviceId = "x-DeviceId"
case clientType = "x-ClientType"
case appInstanceId = "x-InstanceId"
case version = "version"
case addParams = "optionalParams"
}
func getJsonObject() -> [String:String]? {
do {
let encoded = try JSONEncoder().encode(self)
if let json = try JSONSerialization.jsonObject(with: encoded, options: []) as? [String : String] {
return json
}
} catch (let error) {
print("Error building JSON: \(error.localizedDescription)")
}
return nil
}
}
Without the the addParams field, the JSONSerialization works as expected. When I add the addParams field, which adds a nested dictionary to the object, the JSONSerialization fails and returns nil.
Can anyone give me a clue as to why I can't add a nested dictionary in this scenario?
Thanks!
It fails as one key (here it's the added addParams ) 's value isn't a String so the cast
as? [String : String] // causes failure
Won't occur , hence a nil json , so Replace
if let json = try JSONSerialization.jsonObject(with: encoded, options: [])
as? [String : String] {
with
if let json = try JSONSerialization.jsonObject(with: encoded, options: [])
as? [String : Any] {
Any encapsulates String and [String:String]

UTF-8 encoding issue of JSONSerialization

I was trying convert struct to Dictionary in Swift. This was my code:
extension Encodable {
var dictionary: [String: Any]? {
if let data = try? JSONEncoder().encode(self) {
if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
return dict
}
return nil
}
return nil
}
}
This works in most situation. But when I try to convert a nested structure which contains unicode characters such as Chinese, this happened:
struct PersonModel: Codable {
var job: String?
var contacts: [ContactSimpleModel]
var manager: ManagerSimpleModel?
}
struct ContactSimpleModel: Codable {
var relation: String
var name: String
}
struct ManagerSimpleModel: Codable {
var name: String
var age: Int
}
let contact1 = ContactSimpleModel(relation: "朋友", name: "宙斯")
let contact2 = ContactSimpleModel(relation: "同学", name: "奥丁")
let manager = ManagerSimpleModel(name: "拉斐尔", age: 31)
let job = "火枪手"
let person = PersonModel(job: job, contacts: [contact1, contact2], manager: manager)
if let dict = person.dictionary {
print(dict)
}
The result of this code is this:
["contacts": <__NSArrayI 0x600002471980>(
{
name = "\U5b99\U65af";
relation = "\U670b\U53cb";
},
{
name = "\U5965\U4e01";
relation = "\U540c\U5b66";
}
)
, "manager": {
age = 31;
name = "\U62c9\U6590\U5c14";
}, "job": 火枪手]
You can see the result. The Chinese characters in those nested structures were become a utf-8 encoding string. The top-level property "job": 火枪手 is right. But the values in those nested structures were not the original string.
Is this a bug of JSONSerialization? Or how to make it right?
More information. I used the result like this:
var sortedQuery = ""
if let dict = person.dictionary {
sortedQuery = dict.sorted(by: {$0.0 < $1.0})
.map({ "\($0)\($1)" })
.joined(separator: "")
}
It was used to check whether the query was legal. The result is not the same as Java or other platform.
The result is perfectly fine. That's the internal string representation – a pre-Unicode legacy – of an array or dictionary when you print it.
Assign the values to a label or text view and you will see the expected characters.

How to convert Dictionary to JSON in Vapor 3?

In Vapor 1.5 I used to convert Dictionary to JSON as shown below.
How should I do it in Vapor 3.1?
In docs it says I need to create struct type and conform it to codable protocol.
Is there another method that would enable to convert the existing dictionary without creating new struct?
func makeCustomJSON(clientData: DataFromClientChargeWithCard, paymentID:String,customer: String) throws -> JSON {
var dictionaryOfStrings = [String:String]()
dictionaryOfStrings["BookingState"] = "Active"
dictionaryOfStrings["BookingStateTimeStamp"] = clientData.TimeStampBookingSavedInDB
dictionaryOfStrings["ChangesMadeBy"] = "User"
dictionaryOfStrings["PaymentID"] = paymentID
dictionaryOfStrings["DateAndTime"] = clientData.DateAndTime
dictionaryOfStrings["RatePriceClient"] = clientData.RatePriceClient
dictionaryOfStrings["Locality"] = clientData.Locality
dictionaryOfStrings["StripeCustomerID"] = customer
//some more properties below...
let response:JSON
do {
response = try JSON(node: dictionaryOfStrings)
}catch let error as NSError {
let message = "dictionaryOfStrings can't be converted in JSON"
drop.log.error(message)
logErr.prints(message: message, code: error.code)
throw NSError(domain: "errorDictionary", code: 400, userInfo: ["error" : error.localizedDescription])
}
return response
}
If you have a type like the struct that I have defined here you can simply return it, as long as it conforms to Content.
import Vapor
import FluentSQLite
struct Employee: Codable {
var id: Int?
var name: String
init(name:String) {
self.name = name
}
}
extension Employee: SQLiteModel {}
extension Employee: Migration {}
extension Employee: Parameter {}
extension Employee: Content { }
and then you can simply define a route and return it.
router.get("test") { req in
return Employee(name: "Alan")
}
if you want to use dictionary you can use the JSONSerialization and return a string.
router.get("test2") { req -> String in
let employeeDic: [String : Any] = ["name":"Alan", "age":27]
do {
let data = try JSONSerialization.data(withJSONObject: employeeDic, options: .prettyPrinted)
let jsonString = String(data: data, encoding: .utf8)
return jsonString ?? "FAILED"
}catch{
return "ERROR"
}
}

Can't extract the dictionary as [String : Any] from vapor request context

I'm new to server side swift. I'm using vapor for server side swift. In vapor request I need to get JSON as [String: Any] to check a data type of the value like String, Int or Float. But in request the I can't find the exact data type of the value.
drop.post("post") { (request) -> ResponseRepresentable in
guard let name = request.data["value"]?.string else {
throw Abort.badRequest
}
return value
}
In above method, it directly converts and returns the value as String. I need to check it is a String or Int (some other data types too). I can't check by let condition which is given as below.
guard let name = data["value"] as? String else {
\\do something
}
I need to check it is a String or Int (some other data types too). If anyone has the solution, please let me know.
if you try to get Type of data using a type(of:) function, in result you will get optional any like this.
let dic = ["key":"string"] as [String : Any]
let str = dic["key"] //"string"
type(of: str) //Optional<Any>.Type
you have to check with " if let " for all case.
let dic = ["key":"string"] as [String : Any]
if let str = dic["key"] as? String {
print("String value")
} else if let intvalue = dic["key"] as? Int {
print("Int value")
} else if let boolvalue = dic["key"] as? Bool {
print("Bool value")
}
Hope it will help you
Since you're accessing the JSON, I'd do it like this.
guard let json = request.json else {
throw ...
}
if let string = json.string {
// JSON is a string
} else if let object = json.object {
// JSON is a [String: JSON]
}
// continue unwrapping for different data types
I wouldn't recommend using the type(of:) function for this purpose.