Vapor JSON from `[String: Any]` Dictionary - json

If I build a Swift dictionary, i.e. [String: Any] how can I return that as JSON? I tried this, but it gives me the error: Argument labels '(node:)' do not match any available overloads.
drop.get("test") { request in
var data: [String: Any] = [:]
data["name"] = "David"
data["state"] = "CA"
return try JSON(node: data)
}

Convoluted as heck, but this allows you to use [String:Any].makeNode(), as long as the internals are NodeRepresentable, NSNumber based, or NSNull :) --
import Node
enum NodeConversionError : LocalizedError {
case invalidValue(String,Any)
var errorDescription: String? {
switch self {
case .invalidValue(let key, let value): return "Value for \(key) is not NodeRepresentable - " + String(describing: type(of: value))
}
}
}
extension NSNumber : NodeRepresentable {
public func makeNode(context: Context = EmptyNode) throws -> Node {
return Node.number(.double(Double(self)))
}
}
extension NSString : NodeRepresentable {
public func makeNode(context: Context = EmptyNode) throws -> Node {
return Node.string(String(self))
}
}
extension KeyAccessible where Key == String, Value == Any {
public func makeNode(context: Context = EmptyNode) throws -> Node {
var mutable: [String : Node] = [:]
try allItems.forEach { key, value in
if let _ = value as? NSNull {
mutable[key] = Node.null
} else {
guard let nodeable = value as? NodeRepresentable else { throw NodeConversionError.invalidValue(key, value) }
mutable[key] = try nodeable.makeNode()
}
}
return .object(mutable)
}
public func converted<T: NodeInitializable>(to type: T.Type = T.self) throws -> T {
return try makeNode().converted()
}
}
With that header you can:
return try JSON(node: data.makeNode())

JSON cannot be initialized from a [String : Any] dictionary because Any is not convertible to Node.
There are only a limited number of types that Node can be. (See Node source). If you know your objects are all going to be the same type, use a dictionary that only allows that type. So for your example, [String : String].
If you're going to be getting data from the request, you can try using request.json as is used in the documentation here.
EDIT:
Another (possibly better) solution would be to make your dictionary [String: Node] and then you can include any type that conforms to Node. You may have to call the object's makeNode() function to add it to the dictionary though.

Related

Decode JSON single object or array of object dynamically [duplicate]

This question already has an answer here:
Swift 4 JSON Codable - value returned is sometimes an object, others an array
(1 answer)
Closed 3 years ago.
Let's say I have an array of JSON response from the GET method like :
[{
"id":"1",
"Name":"John Doe",
},{
"id":"2",
"Name":"Jane Doe",
}]
And from the POST method using id param I only have 1 object JSON response :
{
"id":"1",
"Name":"John Doe",
}
how can I write a method to decode both the JSON dynamically?
At the moment, this is what I'm using :
func convertJSON<T:Decodable>(result: Any?, model: T.Type) -> T? {
if let res = result {
do {
let data = try JSONSerialization.data(withJSONObject: res, options: JSONSerialization.WritingOptions.prettyPrinted)
return try JSONDecoder().decode(model, from: data)
} catch {
print(error)
return nil
}
} else {
return nil
}
}
The method can be used to decode a single object using dynamic model, but I just can't figure it out to handle a single object / an array of objects dynamically.
The most I can get with is just using a duplicate of the method but replacing T with
[T] in the method parameter and return type, if the response is an array.
I'm open to any suggestion, any help is appreciated, Thank You in advance.
Edit : If this question is duplicate of this , I'm not sure how the marked answer could be a solution.
One solution could be to always return [Model]?.
Inside your function first try to decode as Model, on success return an array with that single decoded object inside it. If this fails then try to decode as [Model], on success return the decoded object else return nil.
Using your sample JSONs I created a struct:
struct Person: Codable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id
case name = "Name"
}
}
Then I created a struct with a couple of methods to decode from either a String or an optional Data.
struct Json2Type<T: Decodable> {
// From data to type T
static public func convertJson(_ data: Data?) -> [T]? {
// Check data is not nil
guard let data = data else { return nil }
let decoder = JSONDecoder()
// First try to decode as a single object
if let singleObject = try? decoder.decode(T.self, from: data) {
// On success return the single object inside an array
return [singleObject]
}
// Try to decode as multiple objects
guard let multipleObjects = try? decoder.decode([T].self, from: data) else { return nil }
return multipleObjects
}
// Another function to decode from String
static public func convertJson(_ string: String) -> [T]? {
return convertJson(string.data(using: .utf8))
}
}
Finally call the method you prefer:
Json2Type<Person>.convertJson(JsonAsDataOrString)
Update: #odin_123, a way to have either a Model or [Model] as return value can be accomplish using an enum. We can even add the error condition there to avoid returning optionals. Let's define the enum as:
enum SingleMulipleResult<T> {
case single(T)
case multiple([T])
case error
}
Then the struct changes to something like this:
struct Json2Type<T: Decodable> {
static public func convertJson(_ data: Data?) -> SingleMulipleResult<T> {
guard let data = data else { return .error }
let decoder = JSONDecoder()
if let singleObject = try? decoder.decode(T.self, from: data) {
return .single(singleObject)
}
guard let multipleObjects = try? decoder.decode([T].self, from: data) else { return .error }
return .multiple(multipleObjects)
}
static public func convertJson(_ string: String) -> SingleMulipleResult<T> {
return convertJson(string.data(using: .utf8))
}
}
You can call it the same way we did before:
let response = Json2Type<Person>.convertJson(JsonAsDataOrString)
And use a switch to check every possible response value:
switch(response) {
case .single(let object):
print("One value: \(object)")
case .multiple(let objects):
print("Multiple values: \(objects)")
case .error:
print("Error!!!!")
}

How to format a JSON String in Swift?

I have a JSON String that follows a format like { name: "John" } and not { "name" : "John"} and that is resulting in a nil whenever I try to access the name key because:
Error Domain=NSCocoaErrorDomain Code=3840 "No string key for value in object around character 1."
I am looking for a function that fixes/parses/formats this JSON file into something readable? How do sites like JSON Format do it?
Funny enough, { name: "John" } makes a valid JSON object in Javascript. So your problem now becomes finding a Javascript intepreter for Swift!
There's one built-in to recent versions of Mac OS X and iOS: WKWebView. It's a web rendering engine with a Javascript parser. Link your target with WebKit and try this:
import WebKit
class MyJSONParser {
private static let webView = WKWebView()
class func parse(jsonString: String, completionHandler: (AnyObject?, NSError?) -> Void) {
self.webView.evaluateJavaScript(jsonString, completionHandler: completionHandler)
}
}
Usage:
let str = "{ firstName: 'John', lastName: 'Smith' }"
// You must assign the JSON string to a variable or the Javascript
// will return void. Note that this runs asynchronously
MyJSONParser.parse("tmp = \(str)") { result, error in
guard error == nil else {
print(error)
return
}
if let dict = result as? [String: String] {
print(dict)
} else {
print("Can't convert to Dictionary")
}
}
Swift 3
import WebKit
class MyJSONParser {
private static let webView = WKWebView()
class func parse(jsonString: String, completionHandler: #escaping (Any?, Error?) -> Void) {
self.webView.evaluateJavaScript(jsonString, completionHandler: completionHandler)
}
}
let str = "{ firstName: 'John', lastName: 'Smith' }"
// You must assign the JSON string to a variable or the Javascript
// will return void. Note that this runs asynchronously
MyJSONParser.parse(jsonString: "tmp = \(str)") { result, error in
guard error == nil else {
print(error!)
return
}
if let dict = result as? [String: String] {
print(dict)
} else {
print("Can't convert to Dictionary")
}
}

How to convert a Swift object to a dictionary

I'm relatively new to iOS programming. However, I would have assumed that Swift would have an automated way of converting objects to JSON and vice versa. That being said, I have found several libraries that can do this.
HOWEVER...
It seems that no matter how you post data to a web service (even using something like AlamoFire), the requests must be a dictionary. All these forums show examples of how easy it is to convert the returned JSON string to objects. True. But the request needs to be manually coded. That is, go through all of the object properties and map them as a dictionary.
So my question is this: Am I missing something? Have I got this all wrong and there's a super-easy way to either (a) send JSON (instead of a dictionary) in the REQUEST or (b) convert an object automatically to a dictionary?
Again, I see how easy it is to deal with a JSON response. I'm just looking for an automatic way to convert the request object I want to post to a web service into a format that a library like AlamoFire (or whatever) requires. With other languages this is fairly trivial, so I'm hoping there's an equally easy and automated way with Swift.
I must disagree with #Darko.
In Swift 2,
use protocol oriented programming and the simple reflection offered by Mirror class :
protocol JSONAble {}
extension JSONAble {
func toDict() -> [String:Any] {
var dict = [String:Any]()
let otherSelf = Mirror(reflecting: self)
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value
}
}
return dict
}
}
then you can use this protocol with your request class and produce the desired dictionary :
class JsonRequest : JSONAble {
var param1 : String?
// ...
}
let request = JsonRequest()
// set params of the request
let dict = request.toDict()
// use your dict
My solution to this will be something like this:
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 usage will be something like this:
movies.compactMap { $0.dict }
Swift currently does not support advanced reflection like Java or C# so the answer is: no, there is not an equally easy and automated way with pure Swift.
[Update] Swift 4 has meanwhile the Codable protocol which allows serializing to/from JSON and PLIST.
typealias Codable = Decodable & Encodable
Without using reflection, and works for nested objects (Swift 4):
protocol Serializable {
var properties:Array<String> { get }
func valueForKey(key: String) -> Any?
func toDictionary() -> [String:Any]
}
extension Serializable {
func toDictionary() -> [String:Any] {
var dict:[String:Any] = [:]
for prop in self.properties {
if let val = self.valueForKey(key: prop) as? String {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Int {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Double {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Array<String> {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Serializable {
dict[prop] = val.toDictionary()
} else if let val = self.valueForKey(key: prop) as? Array<Serializable> {
var arr = Array<[String:Any]>()
for item in (val as Array<Serializable>) {
arr.append(item.toDictionary())
}
dict[prop] = arr
}
}
return dict
}
}
Just implement properties and valueForKey for the custom objects you want to convert. For example:
class Question {
let title:String
let answer:Int
init(title:String, answer:Int) {
self.title = title
self.answer = answer
}
}
extension Question : Serializable {
var properties: Array<String> {
return ["title", "answer"]
}
func valueForKey(key: String) -> Any? {
switch key {
case "title":
return title
case "answer":
return answer
default:
return nil
}
}
}
You can add more value types in the toDictionary function if you need.
The latest solution that I found after lots of digging throughout Stack Overflow is:
//This block of code used to convert object models to json string
let jsonData = try JSONEncoder().encode(requestData)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
//This method is used to convert jsonstring to dictionary [String:Any]
func jsonToDictionary(from text: String) -> [String: Any]? {
guard let data = text.data(using: .utf8) else { return nil }
let anyResult = try? JSONSerialization.jsonObject(with: data, options: [])
return anyResult as? [String: Any]
}
//Use above method something like this
let params = jsonToDictionary(from: jsonString) ?? [String : Any]()
//Use params to pass in paramters
Alamofire.request(completeUrl, method: .post, parameters: params, encoding:JSONEncoding.prettyPrinted, headers: myHeaders){
response in
//Do whatever you want with response of it.
}
Note:
I combine this solution from multiple answers.
This solution i used with alamofire because alamofire only accept parameter at this format "[String:Any]".
You can also use the ObjectMapper library. It has a "toJSON" method that converts your object to a dictionary.
in short
let dict = Mirror(reflecting: self).children.map({ $0 }).reduce(into: [:]) { $0[$1.label] = $1.value }
Example how to use Mirror with conversion to specific Dictionary type:
protocol DictionaryConvertible { }
extension DictionaryConvertible {
func toDictionary() -> [String: CustomStringConvertible] {
Dictionary(
uniqueKeysWithValues: Mirror(reflecting: self).children
.compactMap { child in
if let label = child.label,
let value = child.value as? CustomStringConvertible {
return (label, value)
} else {
return nil
}
}
)
}
}

Best way to convert JSON or other untyped data into typed classes in Swift?

I'm trying to parse out JSON into typed classes for safety/convenience, but it's proving very clunky. I wasn't able to find a library or even a post for Swift (Jastor is as close as I got). Here's a fabricated little snippet to illustrate:
// From NSJSONSerialization or similar and casted to an appropriate toplevel type (e.g. Dictionary).
var parsedJson: Dictionary<String, AnyObject> = [ "int" : 1, "nested" : [ "bool" : true ] ]
class TypedObject {
let stringValueWithDefault: String = ""
let intValueRequired: Int
let nestedBoolBroughtToTopLevel: Bool = false
let combinedIntRequired: Int
init(fromParsedJson json: NSDictionary) {
if let parsedStringValue = json["string"] as? String {
self.stringValueWithDefault = parsedStringValue
}
if let parsedIntValue = json["int"] as? Int {
self.intValueRequired = parsedIntValue
} else {
// Raise an exception...?
}
// Optional-chaining is actually pretty nice for this; it keeps the blocks from nesting absurdly.
if let parsedBool = json["nested"]?["bool"] as? Bool {
self.nestedBoolBroughtToTopLevel = parsedBool
}
if let parsedFirstInt = json["firstInt"] as? Int {
if let parsedSecondInt = json["secondInt"] as? Int {
self.combinedIntRequired = parsedFirstInt * parsedSecondInt
}
}
// Most succinct way to error if we weren't able to construct self.combinedIntRequired?
}
}
TypedObject(fromParsedJson: parsedJson)
There's a number of issues here that I'm hoping to work around:
It's extremely verbose, since I need to wrap every single property in a copy-pasted if-let for safety.
I'm not sure how to communicate errors when required properties are missing (as noted above). Swift seems to prefer (?) using exceptions for show-stopping problems (rather than pedestrian malformed data as here).
I don't know a nice way to deal with properties that exist but are the wrong type (given that the as? casting will fail and simply skip the block, it's not very informative to the user).
If I want to translate a few properties into a single one, I need to nest the let blocks proportional to the number of properties I'm combining. (This is probably more generally a problem with combining multiple optionals into one value safely).
In general, I'm writing imperative parsing logic when I feel like I ought to be able to do something a little more declarative (either with some stated JSON schema or at least inferring the schema from the class definition).
I do this using the Jastor framework:
1) Implement a Protocol that has a single function that returns an NSDictionary response:
protocol APIProtocol {
func didReceiveResponse(results: NSDictionary)
}
2) Create an API class that defines an NSURLConnection object that can be used as a Request URL for iOS's networking API. This class is created to simply return a payload from the itunes.apple.com API.
class API: NSObject {
var data: NSMutableData = NSMutableData()
var delegate: APIProtocol?
func searchItunesFor(searchTerm: String) {
// Clean up the search terms by replacing spaces with +
var itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+",
options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
var escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
var urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music"
var url: NSURL = NSURL(string: urlPath)
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
println("Search iTunes API at URL \(url)")
connection.start()
}
// NSURLConnection Connection failed.
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
println("Failed with error:\(error.localizedDescription)")
}
// New request so we need to clear the data object.
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
self.data = NSMutableData()
}
// Append incoming data.
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
self.data.appendData(data)
}
// NSURLConnection delegate function.
func connectionDidFinishLoading(connection: NSURLConnection!) {
// Finished receiving data and convert it to a JSON object.
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
delegate?.didReceiveResponse(jsonResult)
}
}
3) Create a class with associated properties that inherits from Jastor
NSDictionary response:
{
"resultCount" : 50,
"results" : [
{
"collectionExplicitness" : "notExplicit",
"discCount" : 1,
"artworkUrl60" : "http:\/\/a4.mzstatic.com\/us\/r30\/Features\/2a\/b7\/da\/dj.kkirmfzh.60x60-50.jpg",
"collectionCensoredName" : "Changes in Latitudes, Changes in Attitudes (Ultmate Master Disk Gold CD Reissue)"
}
]
}
Music.swift
class Music : Jastor {
var resultCount: NSNumber = 0
}
4) Then in your ViewController be sure to set the delegate to self and then make a call to the API's searchITunesFor() method.
var api: API = API()
override func viewDidLoad() {
api.delegate = self;
api.searchItunesFor("Led Zeppelin")
}
5) Implement the Delegate method for didReceiveResponse(). Jastor extends your class to set a NSDictionary of the results returned from the iTunes API.
// #pragma - API Delegates
func didReceiveResponse(results: NSDictionary) {
let music = Music(dictionary: results)
println(music)
}
Short version: Since init isn't allowed to fail, validation has to happen outside of it. Optionals seem to be the intended tool for flow control in these cases. My solution is to use a factory method that returns an optional of the class, and use option chaining inside it to extract and validate the fields.
Note also that Int and Bool aren't children of AnyObject; data coming from an NSDictionary will have them stored as NSNumbers, which can't be cast directly to Swift types. Thus the calls to .integerValue and .boolValue.
Long version:
// Start with NSDictionary since that's what NSJSONSerialization will give us
var invalidJson: NSDictionary = [ "int" : 1, "nested" : [ "bool" : true ] ]
var validJson: NSDictionary = [
"int" : 1,
"nested" : [ "bool" : true ],
"firstInt" : 3,
"secondInt" : 5
]
class TypedObject {
let stringValueWithDefault: String = ""
let intValueRequired: Int
let nestedBoolBroughtToTopLevel: Bool = false
let combinedIntRequired: Int
init(intValue: Int, combinedInt: Int, stringValue: String?, nestedBool: Bool?) {
self.intValueRequired = intValue
self.combinedIntRequired = combinedInt
// Use Optionals for the non-required parameters so
// we know whether to leave the default values in place
if let s = stringValue {
self.stringValueWithDefault = s
}
if let n = nestedBool {
self.nestedBoolBroughtToTopLevel = n
}
}
class func createFromDictionary(json: Dictionary<String, AnyObject>) -> TypedObject? {
// Validate required fields
var intValue: Int
if let x = (json["int"]? as? NSNumber)?.integerValue {
intValue = x
} else {
return nil
}
var combinedInt: Int
let firstInt = (json["firstInt"]? as? NSNumber)?.integerValue
let secondInt = (json["secondInt"]? as? NSNumber)?.integerValue
switch (firstInt, secondInt) {
case (.Some(let first), .Some(let second)):
combinedInt = first * second
default:
return nil
}
// Extract optional fields
// For some reason the compiler didn't like casting from AnyObject to String directly
let stringValue = json["string"]? as? NSString as? String
let nestedBool = (json["nested"]?["bool"]? as? NSNumber)?.boolValue
return TypedObject(intValue: intValue, combinedInt: combinedInt, stringValue: stringValue, nestedBool: nestedBool)
}
class func createFromDictionary(json: NSDictionary) -> TypedObject? {
// Manually doing this cast since it works, and the only thing Apple's docs
// currently say about bridging Cocoa and Dictionaries is "Information forthcoming"
return TypedObject.createFromDictionary(json as Dictionary<String, AnyObject>)
}
}
TypedObject.createFromDictionary(invalidJson) // nil
TypedObject.createFromDictionary(validJson) // it works!
I've also done the following to convert to/from:
class Image {
var _id = String()
var title = String()
var subTitle = String()
var imageId = String()
func toDictionary(dict dictionary: NSDictionary) {
self._id = dictionary["_id"] as String
self.title = dictionary["title"] as String
self.subTitle = dictionary["subTitle"] as String
self.imageId = dictionary["imageId"] as String
}
func safeSet(d: NSMutableDictionary, k: String, v: String) {
if (v != nil) {
d[k] = v
}
}
func toDictionary() -> NSDictionary {
let jsonable = NSMutableDictionary()
self.safeSet(jsonable, k: "title", v: self.title);
self.safeSet(jsonable, k: "subTitle", v: self.subTitle);
self.safeSet(jsonable, k: "imageId", v: self.imageId);
return jsonable
}
}
Then I simply do the following:
// data (from service)
let responseArray = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSArray
self.objects = NSMutableArray()
for item: AnyObject in responseArray {
var image = Image()
image.toDictionary(dict: item as NSDictionary)
self.objects.addObject(image)
}
If you want to POST the data:
var image = Image()
image.title = "title"
image.subTitle = "subTitle"
image.imageId = "imageId"
let data = NSJSONSerialization.dataWithJSONObject(image.toDictionary(), options: .PrettyPrinted, error: nil) as NSData
// data (to service)
request.HTTPBody = data;

Deserialize JSON / NSDictionary to Swift objects

Is there a way to properly deserialize a JSON response to Swift objects resp. using DTOs as containers for fixed JSON APIs?
Something similar to http://james.newtonking.com/json or something like this example from Java
User user = jsonResponse.readEntity(User.class);
whereby jsonResponse.toString() is something like
{
"name": "myUser",
"email": "user#example.com",
"password": "passwordHash"
}
SWIFT 4 Update
Since you give a very simple JSON object the code prepared for to handle that model. If you need more complicated JSON models you need to improve this sample.
Your Custom Object
class Person : NSObject {
var name : String = ""
var email : String = ""
var password : String = ""
init(JSONString: String) {
super.init()
var error : NSError?
let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let JSONDictionary: Dictionary = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) as NSDictionary
// Loop
for (key, value) in JSONDictionary {
let keyName = key as String
let keyValue: String = value as String
// If property exists
if (self.respondsToSelector(NSSelectorFromString(keyName))) {
self.setValue(keyValue, forKey: keyName)
}
}
// Or you can do it with using
// self.setValuesForKeysWithDictionary(JSONDictionary)
// instead of loop method above
}
}
And this is how you invoke your custom class with JSON string.
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = "{ \"name\":\"myUser\", \"email\":\"user#example.com\", \"password\":\"passwordHash\" }"
var aPerson : Person = Person(JSONString: jsonString)
println(aPerson.name) // Output is "myUser"
}
I recommend that you use code generation (http://www.json4swift.com) to create native models out of the json response, this will save your time of parsing by hand and reduce the risk of errors due to mistaken keys, all elements will be accessible by model properties, this will be purely native and the models will make more sense rather checking the keys.
Your conversion will be as simple as:
let userObject = UserClass(userDictionary)
print(userObject!.name)
Swift 2: I really like the previous post of Mohacs! To make it more object oriented, i wrote a matching Extension:
extension NSObject{
convenience init(jsonStr:String) {
self.init()
if let jsonData = jsonStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
{
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as! [String: AnyObject]
// Loop
for (key, value) in json {
let keyName = key as String
let keyValue: String = value as! String
// If property exists
if (self.respondsToSelector(NSSelectorFromString(keyName))) {
self.setValue(keyValue, forKey: keyName)
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
else
{
print("json is of wrong format!")
}
}
}
custom classes:
class Person : NSObject {
var name : String?
var email : String?
var password : String?
}
class Address : NSObject {
var city : String?
var zip : String?
}
invoking custom classes with JSON string:
var jsonString = "{ \"name\":\"myUser\", \"email\":\"user#example.com\", \"password\":\"passwordHash\" }"
let aPerson = Person(jsonStr: jsonString)
print(aPerson.name!) // Output is "myUser"
jsonString = "{ \"city\":\"Berlin\", \"zip\":\"12345\" }"
let aAddress = Address(jsonStr: jsonString)
print(aAddress.city!) // Output is "Berlin"
Yet another JSON handler I wrote:
https://github.com/dankogai/swift-json
With it you can go like this:
let obj:[String:AnyObject] = [
"array": [JSON.null, false, 0, "", [], [:]],
"object":[
"null": JSON.null,
"bool": true,
"int": 42,
"double": 3.141592653589793,
"string": "a α\t弾\n𪚲",
"array": [],
"object": [:]
],
"url":"http://blog.livedoor.com/dankogai/"
]
let json = JSON(obj)
json.toString()
json["object"]["null"].asNull // NSNull()
json["object"]["bool"].asBool // true
json["object"]["int"].asInt // 42
json["object"]["double"].asDouble // 3.141592653589793
json["object"]["string"].asString // "a α\t弾\n𪚲"
json["array"][0].asNull // NSNull()
json["array"][1].asBool // false
json["array"][2].asInt // 0
json["array"][3].asString // ""
As you see no !? needed between subscripts.
In addition to that you can apply your own schema like this:
//// schema by subclassing
class MyJSON : JSON {
override init(_ obj:AnyObject){ super.init(obj) }
override init(_ json:JSON) { super.init(json) }
var null :NSNull? { return self["null"].asNull }
var bool :Bool? { return self["bool"].asBool }
var int :Int? { return self["int"].asInt }
var double:Double? { return self["double"].asDouble }
var string:String? { return self["string"].asString }
var url: String? { return self["url"].asString }
var array :MyJSON { return MyJSON(self["array"]) }
var object:MyJSON { return MyJSON(self["object"]) }
}
let myjson = MyJSON(obj)
myjson.object.null // NSNull?
myjson.object.bool // Bool?
myjson.object.int // Int?
myjson.object.double // Double?
myjson.object.string // String?
myjson.url // String?
There's a great example by Apple for deserializing JSON with Swift 2.0
The trick is to use the guard keyword and chain the assignments like so:
init?(attributes: [String : AnyObject]) {
guard let name = attributes["name"] as? String,
let coordinates = attributes["coordinates"] as? [String: Double],
let latitude = coordinates["lat"],
let longitude = coordinates["lng"],
else {
return nil
}
self.name = name
self.coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
I personally prefer native parsing vs any 3rd party, as it is transparent and magic-less. (and bug less?)
Using quicktype, I generated your model and serialization helpers from your sample:
import Foundation
struct User: Codable {
let name: String
let email: String
let password: String
}
extension User {
static func from(json: String, using encoding: String.Encoding = .utf8) -> OtherUser? {
guard let data = json.data(using: encoding) else { return nil }
return OtherUser.from(data: data)
}
static func from(data: Data) -> OtherUser? {
let decoder = JSONDecoder()
return try? decoder.decode(OtherUser.self, from: data)
}
var jsonData: Data? {
let encoder = JSONEncoder()
return try? encoder.encode(self)
}
var jsonString: String? {
guard let data = self.jsonData else { return nil }
return String(data: data, encoding: .utf8)
}
}
Then parse User values like this:
let user = User.from(json: """{
"name": "myUser",
"email": "user#example.com",
"password": "passwordHash"
}""")!
I wrote this small open-source library recently that lets you quickly and easily deserialize dictionaries into Swift objects: https://github.com/isair/JSONHelper
Using it, deserializing data becomes as easy as this:
var myInstance = MyClass(data: jsonDictionary)
or
myInstance <-- jsonDictionary
And models need to look only like this:
struct SomeObjectType: Deserializable {
var someProperty: Int?
var someOtherProperty: AnotherObjectType?
var yetAnotherProperty: [YetAnotherObjectType]?
init(data: [String: AnyObject]) {
someProperty <-- data["some_key"]
someOtherProperty <-- data["some_other_key"]
yetAnotherProperty <-- data["yet_another_key"]
}
}
Which, in your case, would be:
struct Person: Deserializable {
var name: String?
var email: String?
var password: String?
init(data: [String: AnyObject]) {
name <-- data["name"]
email <-- data["email"]
password <-- data["password"]
}
}
If you would like parse from and to json without the need to manually map keys and fields, then you could also use EVReflection. You can then use code like:
var user:User = User(json:jsonString)
or
var jsonString:String = user.toJsonString()
The only thing you need to do is to use EVObject as your data objects base class.
See the GitHub page for more detailed sample code
I am expanding upon Mohacs and Peter Kreinz's excellent answers just a bit to cover the array of like objects case where each object contains a mixture of valid JSON data types. If the JSON data one is parsing is an array of like objects containing a mixture of JSON data types, the do loop for parsing the JSON data becomes this.
// Array of parsed objects
var parsedObjects = [ParsedObject]()
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as [Dictionary<String, AnyObject>]
// Loop through objects
for dict in json {
// ParsedObject is a single instance of an object inside the JSON data
// Its properties are a mixture of String, Int, Double and Bool
let parsedObject = ParsedObject()
// Loop through key/values in object parsed from JSON
for (key, value) in json {
// If property exists, set the value
if (parsedObject.respondsToSelector(NSSelectorFromString(keyName))) {
// setValue can handle AnyObject when assigning property value
parsedObject.setValue(keyValue, forKey: keyName)
}
}
parsedObjects.append(parsedObject)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
This way lets you get the user from a URL. It's parse the NSData to a NSDictionary and then to your NSObject.
let urlS = "http://api.localhost:3000/"
func getUser(username: Strung) -> User {
var user = User()
let url = NSURL(string: "\(urlS)\(username)")
if let data = NSData(contentsOfURL: url!) {
setKeysAndValues(user, dictionary: parseData(data))
}
return user
}
func setKeysAndValues (object : AnyObject, dictionary : NSDictionary) -> AnyObject {
for (key, value) in dictionary {
if let key = key as? String, let value = value as? String {
if (object.respondsToSelector(NSSelectorFromString(key))) {
object.setValue(value, forKey: key)
}
}
}
return object
}
func parseData (data : NSData) -> NSDictionary {
var error: NSError?
return NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
}
In Swift 4, You can use the Decoding, CodingKey protocols to deserialize the JSON response:
Create the class which confirm the decodable protocol
class UserInfo: Decodable
Create members of the class
var name: String
var email: String
var password: String
Create JSON key enum which inherits from CodingKey
enum UserInfoCodingKey: String, CodingKey {
case name
case password
case emailId
}
Implement init
required init(from decoder: Decoder) throws
The whole class look like :
Call Decoder
// jsonData is JSON response and we get the userInfo object
let userInfo = try JsonDecoder().decode(UserInfo.self, from: jsonData)
You do this by using NSJSONSerialization. Where data is your JSON.
First wrap it in an if statement to provide some error handling capablity
if let data = data,
json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] {
// Do stuff
} else {
// Do stuff
print("No Data :/")
}
then assign them:
let email = json["email"] as? String
let name = json["name"] as? String
let password = json["password"] as? String
Now, This will show you the result:
print("Found User iname: \(name) with email: \(email) and pass \(password)")
Taken from this Swift Parse JSON tutorial. You should check out the tutorial as it goes a lot more in depth and covers better error handling.