Swift - Create data model from JSON response - json

I'm learning Swift lang and one of the things that would be great to hear others input about is "How you handle models from JSON responses"? For example -
I have User.swift model:
class User: NSObject {
var user_token:String?
var email:String?
}
and also I would like to use KeyValueObjectMapping as I do in Obj-C projects. Unfortunately this doesn't work here:
let parser = DCKeyValueObjectMapping.mapperForClass(User)
let user = parser.parseDictionary(data.objectForKey("user") as NSDictionary) as User
println(user.user_token) // returns nil
How do you create your models in Swift?

I recommend using code generation to generate models in Swift based on JSON. To that end, I've created a tool at http://www.guideluxe.com/JsonToSwift to make modeling and parsing JSON as easy as possible.
After you've submited a sample JSON object with a class name to the tool, it will generate a corresponding Swift class, as well as any needed subsidiary Swift classes, to represent the structure implied by the sample JSON. Also included are class methods used to populate Swift objects, including one that utilizes the NSJSONSerialization.JSONObjectWithData method. The necessary mappings from the NSArray and NSDictionary objects are provided.
After copying the generated code into your project as a Swift class(es), you only need to supply an NSData object containing JSON that matches the sample provided to the tool.
Other than Foundation, there are no dependencies.
Here's how to create an NSData object from a JSON file to test with.
let fileUrl: NSURL = NSBundle.mainBundle().URLForResource("JsonFile", withExtension: "json")!
let jsonData: NSData = NSData(contentsOfURL: fileUrl)!

I would suggest using SwiftyJSONModel there your model would look something like:
import SwiftyJSONModel
class User: NSObject, JSONObjectInitializable {
enum PropertyKey : String {
case user_token, email
}
var user_token:String?
var email:String?
required init(object: JSONObject<PropertyKey>) throws {
user_token = object.value(for: .user_token)
email = object.value(for: .email)
}
}
This library has 3 nice things:
You don't have to explicitly cast to String as library will infer the type
You can have non-optional properties and library will tell you which exact field was wrong
All the keys to the model are incapsulated in enum which gives you auto-complition when you type the keys and guarantees that you cannot access keys, that are not in enum

I'm using jsoncafe easiest and customizable template base model class generator with different framwroks like SwiftyJSON, Codable, Gloss, Simple Swift Class even you can make your own template
jsoncafe.com

Here is some example code for Model Class and parsing JSON response with out any library.
Model Class
class User: NSObject{
var user_token: String = ""
var email: String = ""
}
Example code to call web-service api and Parsing.
NSURLConnection.sendAsynchronousRequest(request1, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var err: NSError
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
//println("Result : \(jsonResult)")
let model = User()
model. user_token = jsonResult["user_token"] as NSString
model. email = jsonResult["email"] as NSString
})

If you want a more rigorous approach and have access to JSON schemas as meta description for your JSON documents, I wrote a code generator which can handle those (JSON schema, draft 4):
https://github.com/werner77/MappableObjectGenerator
My tools supports basically any programming language, because it is based on code generation templates, but is focussed on ObjectiveC and Swift 4 support right now.

May be its too late.
You can also try using the link
http://www.json4swift.com/results.php
Where in you just need to paste the JSON values and it gives you the swift files instead.

Related

Receiving Websocket data in Swift

I'm carrying this on from this question, since the focus has changed.
I am trying to send string data from a vapor server over a websocket. The client side is where the main question is. This code successfully receives the string, which is expected to be JSON (but not absolutely guaranteed -- out of scope).
switch message {
case .data(let data):
print("data: \(data)")
case .string(let str):
// let data = str.message(using: .utf8)
let jsonData = Data(str.utf8)
print("string: \(jsonData)")
do {
struct Person : Codable {
var name: String
}
let decoder = JSONDecoder()
let people = try decoder.decode([Person].self, from: jsonData)
print("result: \(people)")
} catch {
print(error.localizedDescription)
}
}
After some very helpful guidance, sending a string such as "{\"name\": \"Bobberoo\"}" will print out
string: 20 bytes
The data couldn’t be read because it isn’t in the correct format.
If I wrap it in braces "[{\"name\": \"Bobberoo\"}]" produces the more helpful but still mystifing (to me) output:
result: [wb2_socket_client.WebSocketController.(unknown context at $101a35028).(unknown context at $101a350c0).(unknown context at $101a35158).Person(name: "Bobberoo")]
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they? I can see that the first is the instance of the WebSocketController. How do I access this data.
And as a non-inflammatory aside: managing JSON is a trivial operation in any number of contexts. Python/Flask, Node, Ruby/Rails and on and on; I've used all these and implementing this kind of interaction is trivial. In Swift, it's a horrible, underdocumented nightmare. At least, that's my experience. Why? I know the language is type safe, but this is ridiculous.
error.localizedDescription won't give you an error message that is useful message for debugging. On the other hand, if you print error directly:
print(error)
You'd get something along the lines of "expected to decode array but found dictionary instead", which is exactly what is happening in the case of
{
"name": "Bobberoo"
}
You are decoding a [Person].self, i.e. an array of Person, but your JSON root is not a JSON array. The above JSON can be decoded if you did:
let people = try decoder.decode(Person.self, from: jsonData)
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they?
This is the default string representation of a type. Your Person struct does not conform to CustomStringConvertible or CustomDebugStringConvertible or TextOutputStreamable, so "an unspecified result is supplied automatically by the Swift standard library" (the link points to String.init(reflecting:), which presumably gets called somewhere along the way when you print the array of Person) and used as the string representation.
From what I can see, its current implementation is the fully qualified name of the struct - starting with the module, then the top-level class, then each enclosing scope, ending with the struct name, followed by the struct's members in brackets. It turns out that the enclosing scopes has no "names", and so are just called (unknown context at xxxxx). This is all very much implementation details, and things that you shouldn't care about.
What you should do, is provide an implementation of CustomStringConvertible:
struct Person: CustomStringConvertible {
...
var description: String { "name: \(name)" }
}
Now printing people gives:
[name: Bobberoo]
I can see that the first is the instance of the WebSocketController.
No. The WebSocketController is part of the fully qualified name of your Person struct. There is exactly one instance in your decoded array, and it's an instance of Person, as you would expect!
How do I access this data?
To access its name:
if let firstPerson = people.first {
let firstPersonsName = firstPerson.name
}

escaped json string on Swift

In an iPhone app using SWIFT, I'm having to deal with a third-party API that sends a escaped string instead of a JSON object as response.
The response looks like this:
"[
{
\"ID\":3880,
\"Name\":\"Exploration And Production Inc.\",
\"ContractNumber\":\"123-123\",
\"Location\":\"Booker #1\",
\"Volume\":1225.75,
\"OtherFees\":10.0
}
]"
Up until now I have been dealing with this by manipulating the string to remove the unwanted characters until I get a JSON-like string and then parsing that as usual.
Angular has a handy function to deal with this:
angular.fromJson(response.data);
Java has its own way to deal with it. Is an equivalent function in Swift?
If you are parsing it to a dictionary then the simplest solution will be to convert String to Data and use JSONSerialization:
if let data = string.data(using: .utf8) {
do {
let responseArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
print(responseArray)
}
catch {
print(error)
}
}
But ofcourse it would be better to process it as a Codable model in which case its again just as simple as:
try JSONDecoder().decode([Item].self, from: data)
Provided that you have a valid Decodable model like so:
struct Item: Decodable {
let id: Int
let name: String
//add the other keys you want to use (note its type sensitive)
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
}
}
Lastly, avoid stringified jsons because its an easy source of errors.
Malformed strings or small/large deviations in the structure can go easily unnoticed.
Let the backend team know that they should follow a protocol in their API that its consumers can rely on.
By setting the json format, its essentially like a contract that showcases its content and purpose with clarity.
Sending a stringified json is simply lazy and reflects poorly on its designer, imho.

How to parse JSON into NSManagedObject with Codable? [duplicate]

Codable seems a very exciting feature. But I wonder how we can use it in Core Data? In particular, is it possible to directly encode/decode a JSON from/to a NSManagedObject?
I tried a very simple example:
and defined Foo myself:
import CoreData
#objc(Foo)
public class Foo: NSManagedObject, Codable {}
But when using it like this:
let json = """
{
"name": "foo",
"bars": [{
"name": "bar1",
}], [{
"name": "bar2"
}]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let foo = try! decoder.decode(Foo.self, from: json)
print(foo)
The compiler failed with this errror:
super.init isn't called on all paths before returning from initializer
and the target file was the file that defined Foo
I guess I probably did it wrong, since I didn't even pass a NSManagedObjectContext, but I have no idea where to stick it.
Does Core Data support Codable?
You can use the Codable interface with CoreData objects to encode and decode data, however it's not as automatic as when used with plain old swift objects. Here's how you can implement JSON Decoding directly with Core Data objects:
First, you make your object implement Codable. This interface must be defined on the object, and not in an extension. You can also define your Coding Keys in this class.
class MyManagedObject: NSManagedObject, Codable {
#NSManaged var property: String?
enum CodingKeys: String, CodingKey {
case property = "json_key"
}
}
Next, you can define the init method. This must also be defined in the class method because the init method is required by the Decodable protocol.
required convenience init(from decoder: Decoder) throws {
}
However, the proper initializer for use with managed objects is:
NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)
So, the secret here is to use the userInfo dictionary to pass in the proper context object into the initializer. To do this, you'll need to extend the CodingUserInfoKey struct with a new key:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}
Now, you can just as the decoder for the context:
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }
self.init(entity: entity, in: context)
let container = decoder.container(keyedBy: CodingKeys.self)
self.property = container.decodeIfPresent(String.self, forKey: .property)
}
Now, when you set up the decoding for Managed Objects, you'll need to pass along the proper context object:
let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context
_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...
try context.save() //make sure to save your data once decoding is complete
To encode data, you'll need to do something similar using the encode protocol function.
CoreData is its own persistence framework and, per its thorough documentation, you must use its designated initializers and follow a rather specific path to creating and storing objects with it.
You can still use Codable with it in limited ways just as you can use NSCoding, however.
One way is to decode an object (or a struct) with either of these protocols and transfer its properties into a new NSManagedObject instance you've created per Core Data's docs.
Another way (which is very common) is to use one of the protocols only for a non-standard object you want to store in a managed object's properties. By "non-standard", I mean anything thst doesn't conform to Core Data's standard attribute types as specified in your model. For example, NSColor can't be stored directly as a Managed Object property since it's not one of the basic attribute types CD supports. Instead, you can use NSKeyedArchiver to serialize the color into an NSData instance and store it as a Data property in the Managed Object. Reverse this process with NSKeyedUnarchiver. That's simplistic and there is a much better way to do this with Core Data (see Transient Attributes) but it illustrates my point.
You could also conceivably adopt Encodable (one of the two protocols that compose Codable - can you guess the name of the other?) to convert a Managed Object instance directly to JSON for sharing but you'd have to specify coding keys and your own custom encode implementation since it won't be auto-synthesized by the compiler with custom coding keys. In this case you'd want to specify only the keys (properties) you want to be included.
Hope this helps.
Swift 4.2:
Following casademora's solution,
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
should be
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }.
This prevents errors that Xcode falsely recognizes as array slice problems.
Edit: Use implicitly unwrapped optionals to remove the need to force unwrap .context every time it is being used.
As an alternative for those who would like to make use of XCode's modern approach to NSManagedObject file generation, I have created a DecoderWrapper class to expose a Decoder object which I then use within my object which conforms to a JSONDecoding protocol:
class DecoderWrapper: Decodable {
let decoder:Decoder
required init(from decoder:Decoder) throws {
self.decoder = decoder
}
}
protocol JSONDecoding {
func decodeWith(_ decoder: Decoder) throws
}
extension JSONDecoding where Self:NSManagedObject {
func decode(json:[String:Any]) throws {
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data)
try decodeWith(wrapper.decoder)
}
}
extension MyCoreDataClass: JSONDecoding {
enum CodingKeys: String, CodingKey {
case name // For example
}
func decodeWith(_ decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
This is probably only useful for models without any non-optional attributes, but it solves my problem of wanting to use Decodable but also manage relationships and persistence with Core Data without having to manually create all my classes / properties.
Edit: Example of it in use
If I have a json object:
let myjson = [ "name" : "Something" ]
I create the object in Core Data (force cast here for brevity):
let myObject = NSEntityDescription.insertNewObject(forEntityName: "MyCoreDataClass", into: myContext) as! MyCoreDataClass
And I use the extension to have the object decode the json:
do {
try myObject.decode(json: myjson)
}
catch {
// handle any error
}
Now myObject.name is "Something"

Convert NSObject to JSON string

I have a class that conforms to NSObject and has fields like NSNumber and NSMutableArray, so cannot really use Codable here.
So the class is like this:
class Custom: NSObject {
var users: NSMutableArray?
var type: NSNumber?
.. and many more fields
}
Now i have an object of this class and want to get JSON string from that object :
I have tried following :
let json = try? JSONSerialization.data(withJSONObject: customObject, options: JSONSerialization.WritingOptions.prettyPrinted) as? [String: Any]
let json = try? JSONSerialization.data(withJSONObject: paymentData, options: JSONSerialization.WritingOptions.prettyPrinted)
The above two code gave crash like Invalid top-level type in JSON write'
And used SwiftyJson library too but that too gave the error SwiftyJSON.SwiftyJSONError.unsupportedType
I wanted to try Codable but that requires me to convert NSNumber to Int and MSMutableArray to Array but I cannot really change this as I have used this code in many other places and also my code is working with Objective C so either I had to use NSNumber or had to convert between Int and NSNumber too many times.
Is there a solution that doesn't require changing the current implementation of class and still convert the object to json.
Any help would be appreciated.

JSON API Document de-serializing and serializing using Spine Swift Library

I'm using Spine A Swift library for working with JSON:API
I want to understand how to serialize the data to take full advantage of the JSON API's
I'm using Serializer to achieve this as:
let serializer = Serializer()
serializer.registerResource(MyModel.self)
What i have got ?
let document = try! serializer.deserializeData(data) //data is my response as NSData
I'm able to get the JSON API Document with all the included relationships and data as
document.included
For this I'm casting MyModel to the required data as
MyModel = document.included[0] as! MyModel
As this is not the intended way to fully utilize the JSON :API, any suggestions and what am i missing ?