JSON and Realm — do I have to maintain separate structs / classes? - json

When I receive JSON from an API call, when decoding it I have structs like this:
import Foundation
struct JSON: Codable {
var alpha: Alpha
var beta: Beta
var gamma: [Gamma]?
}
I want to save the JSON in my Realm database, to later use and traverse like JSON. It's my understanding that I can't just use the existing structs I have written, instead I have to rewrite a second (similar) class like this to use with Realm:
import Foundation
import RealmSwift
class RealmJSON: Object, Identifiable {
#Persisted (primaryKey: true) var id: ObjectId
#Persisted var alpha: RealmAlpha
#Persisted var beta: RealmBeta
#Persisted var gamma: RealmSwift.List<RealmGamma>?
override class func primaryKey() -> String? {
"id"
}
convenience init(id: ObjectId, alpha: RealmAlpha, beta: RealmBeta, gamma: RealmSwift.List<RealmGamma>?) {
self.init()
self.id = id
self.alpha = alpha
self.beta = beta
self.gamma = gamma
}
}
Obviously, this is inconvenient especially when dealing with large amounts of JSON. Moreover I want to use Swagger codegen to write the client code for me, but it kind of defeats the purpose if I then have to manually add the Realm classes manually.
Is this the only way for dealing with JSON and a Realm database, or am I missing something here?
EDIT: I realise a simple way is to store most of the JSON as a raw JSON string with properties to identify schema type / version. Then I can just fetch the correct schema I require and parse the rawJSON string with the existing JSON structs...

You can pass the json data directly to your objects. I can think of two ways.
The first way, conform to Codable.
class Dog: Object, Codable {
#Persisted var name: String
}
class Cat: Object, Codable {
#Persisted var name: String
}
class Kid: Object, Codable {
#Persisted var name: String
}
class Owner: Object, Codable {
#Persisted var name: String
#Persisted var dog: Dog?
#Persisted var cat: Cat?
#Persisted var kids: List<Kid>
}
Let's use the following method to make json data:
func makeData() -> Data {
let string = """
{
"name": "Tom",
"kids": [{"name": "Penelope"}, {"name": "Rob"}],
"cat": {"name": "Lilly"},
"dog": {"name": "Lucy"}
}
"""
return string.data(using: .utf8)!
}
Now we can create our objects:
func decodeOwner() {
let decoder = JSONDecoder()
let owner = try! decoder.decode(Owner.self, from: makeData())
print("Decoded:", owner)
}
Another way is to use JSONSerialization and use the result to pass to the value constructor:
extension Object {
convenience init(json: Data) throws {
let data = try JSONSerialization.jsonObject(with: json)
self.init(value: data)
}
}
func serializeOwner() {
let owner = try! Owner(json: makeData())
print("Serialization:", owner)
}

Related

How to convert dinamically Swift classes to JSON and vice-versa?

In my new project that act like a RPC, in some moment i receive a JSON with function name and a list of parameters. Example:
{
"f": "sample.login",
"p": [
{"n": "param1", "v": "value1"},
{"n": "param2", "v": true},
{"n": "param3", "v": {"id": 1, "title": "title xyz"}}
[...] any amount of params [...]
]
}
In other moments, i need create the same structure and encode as JSON. Example:
public class Param: Codable {
public var n: String
public var v: Any?
init(n: String, v: Any?) {
self.n = n
self.v = v
}
}
struct Todo: Codable {
var id: Int64
var title: String
var data: [String: String]
var done: Bool
}
public class JsonSerializer: Serializer {
private var decoder = JSONDecoder()
private var encoder = JSONEncoder()
public func encodeRequest(functionName: String, params: [Param]) -> String {
do {
let request = JsonRequestData(f: functionName, p: params)
let data = try encoder.encode(request)
if let result = String(data: data, encoding: .utf8) {
return result
} else {
print("[JsonSerializer : encodeRequest] Error when try to encode data")
}
} catch let e {
print("[JsonSerializer : encodeRequest] Error when try to encode data: \(e.localizedDescription)")
}
return ""
}
struct JsonRequestData: Codable {
let f: String
var p: [Param]
init(f: String, p: [Param]) {
self.f = f
self.p = p
}
}
}
let todo = Todo(id: 1, title: "Title 1", data: [:], done: true)
let name = "sample.todo.single"
var params: [Param] = []
params.append(Param(n: "suffix", v: "%"))
params.append(Param(n: "p2", v: todo))
let s = JsonSerializer()
let json = s.encodeRequest(functionName: name, params: params)
print(json)
I made it work in C++ (nlohmann json) and Kotlin (with gson). Only left make it work in Swift.
I know of course Swift doesn't support encoding ANY type. And I'm aware of some limitations on this in Swift.
But I would like to find a plausible solution to my problem.
Even if the user has to implement a protocol on his side for his types, or enter his type in a list of known types or something.
The project is at this URL, if you want to see the codes in more depth:
https://github.com/xplpc/xplpc
Removing this lock, the code is practically ready.
I tried on Apple forums, search on Google and on iOS group inside Slack.
Thanks for answers.
But after try a lot, i decide to use AnyCodable project (https://github.com/Flight-School/AnyCodable) with my modifications (https://github.com/xplpc/xplpc/tree/main/swift/lib/Sources).
AnyCodable let me use all swift types and if i use these types on my class/struct it works without problems.
To use any custom type, only need add more lines on AnyEncodable and AnyDecodable class.
Thanks.

Swift - Using Decodable to decode JSON array of just strings

I have a sample JSON where its just an array of strings and has no keys and would like to use the Decodable protocol to consume the JSON and create a simple model out of it.
The json looks like this:
{ "names": [ "Bob", "Alice", "Sarah"] }
Just a collection of strings in an simple array.
What I'm unsure about is how do I use the new Swift Decodable protocol to read this into a model without a key.
Most of the examples I've seen assume the JSON has a key.
IE:
// code from: Medium article: https://medium.com/#nimjea/json-parsing-in-swift-2498099b78f
struct User: Codable{
var userId: Int
var id: Int
var title: String
var completed: Bool
}
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode([User].self, from:
dataResponse) //Decode JSON Response Data
print(model)
} catch let parsingError {
print("Error", parsingError)
}
This above example assumes that the json is a key-value; how can I use the decodable protocol to de-code the JSON without keys?
With thanks
The corresponding struct of this JSON is
struct User: Decodable {
let names: [String]
}
and decode
let model = try decoder.decode(User.self, from: dataResponse)
and get the names with
let names = model.names
or traditionally without the overhead of JSONDecoder
let model = try JSONSerialization.jsonObject(with: dataResponse) as? [String:[String]]
For this simple structure of json , i guess it's better not to create any structs and use
let model = try decoder.decode([String:[String]].self, from: dataResponse)
print(model["names"])
the json fiting for your model is
{
"names": [{
"userId": 2,
"id": 23,
"title": "gdgg",
"completed": true
}]
}
struct Root: Codable {
let names: [User]
}
struct User: Codable {
let userId, id: Int
let title: String
let completed: Bool
}

Using Swift 4 Decodable to parse JSON with mixed property values

I am trying to update my app to use Swift 4 Decodable - and am pulling down data from a JSON api which has child values which could be:
A Child Object
A String
An Int
Here is the Json Api response:
var jsonoutput =
"""
{
"id": "124549",
"key": "TEST-32",
"fields": {
"lastViewed": "2018-02-17T21:40:38.864+0000",
"timeestimate": 26640
}
}
""".data(using: .utf8)
I have tried to parse it using the following: which works if I just reference the id and key properties which are both strings.
struct SupportDeskResponse: Decodable{
var id: String
var key: String
//var fields: [String: Any] //this is commented out as this approach doesn't work - just generated a decodable protocol error.
}
var myStruct: Any!
do {
myStruct = try JSONDecoder().decode(SupportDeskResponse.self, from: jsonoutput!)
} catch (let error) {
print(error)
}
print(myStruct)
How do I decode the fields object into my Struct?
You should create a new Struct adopting Decodable protocol like this :
struct FieldsResponse: Decodable {
var lastViewed: String
var timeestimate: Int
}
Then you can add it to your SupportDeskResponse
var fields: FieldsResponse

Mapping a JSON object to a Swift class/struct

I need to "replicate" an entiry which is returned from a remote web API service in JSON. It looks like this:
{
"field1": "some_id",
"entity_name" = "Entity1"
"field2": "some name",
"details1": [{
"field1": 11,
"field2": "some value",
"data": {
"key1": "value1",
"key2": "value2",
"key3": "value3",
// any other, unknown at compile time keys
}
}],
"details2": {
"field1": 13,
"field2": "some value2"
}
}
Here's my attempt:
struct Entity1 {
struct Details1 {
let field1: UInt32
let field2: String
let data: [String: String]
}
struct Details2 {
let field1: UInt32
let field2: String
}
let field1: String
static let entityName = "Entity1"
let field2: String
let details1: [Details1]
let details2: Details2
}
Is it a good idea to use structs instead of classes for such a goal
as mine?
Can I anyhow define a nested struct or a class, say
Details1 and create a variable of it at the same time?
Like this:
//doesn't compile
struct Entity1 {
let details1: [Details1 {
let field1: UInt32
let field2: String
let data: [String: String]
}]
You can use any if the following good open-source libraries available to handle the mapping of JSON to Object in Swift, take a look :
Mapper
ObjectMapper
JSONHelper
Argo
Unbox
Each one have nice a good tutorial for beginners.
Regarding the theme of struct or class, you can consider the following text from The Swift Programming Language documentation:
Structure instances are always passed by value, and class
instances are always passed by reference. This means that they are
suited to different kinds of tasks. As you consider the data
constructs and functionality that you need for a project, decide
whether each data construct should be defined as a class or as a
structure.
As a general guideline, consider creating a structure when one or more
of these conditions apply:
The structure’s primary purpose is to encapsulate a few relatively simple data values.
It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an
instance of that structure.
Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
The structure does not need to inherit properties or behavior from another existing type.
Examples of good candidates for structures include:
The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.
In all other cases, define a class, and create instances of that class
to be managed and passed by reference. In practice, this means that
most custom data constructs should be classes, not structures.
I hope this help you.
HandyJSON is exactly what you need. See code example:
struct Animal: HandyJSON {
var name: String?
var id: String?
var num: Int?
}
let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"
if let animal = JSONDeserializer.deserializeFrom(json: jsonString) {
print(animal)
}
https://github.com/alibaba/handyjson
Details
Xcode 10.2.1 (10E1001), Swift 5
Links
Pods:
Alamofire - loading data
More info:
Codable
More samples of usage Codable and ObjectMapper in Swift 5
Task
Get itunes search results using iTunes Search API with simple request https://itunes.apple.com/search?term=jack+johnson
Full sample
import UIKit
import Alamofire
// Itunce api doc: https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#searching
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
private func loadData() {
let urlString = "https://itunes.apple.com/search?term=jack+johnson"
Alamofire.request(urlString).response { response in
guard let data = response.data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(ItunceItems.self, from: data)
print(result)
} catch let error {
print("\(error.localizedDescription)")
}
}
}
}
struct ItunceItems: Codable {
let resultCount: Int
let results: [ItunceItem]
}
struct ItunceItem: Codable {
var wrapperType: String?
var artistId: Int?
var trackName: String?
var trackPrice: Double?
var currency: String?
}
you could use SwiftyJson and let json = JSONValue(dataFromNetworking)
if let userName = json[0]["user"]["name"].string{
//Now you got your value
}
Take a look at this awesome library that perfectly fits your need, Argo on GitHub.
In your case, a struct is ok. You can read more on how to choose between a struct and a class here.
You can go with this extension for Alamofire https://github.com/sua8051/AlamofireMapper
Declare a class or struct:
class UserResponse: Decodable {
var page: Int!
var per_page: Int!
var total: Int!
var total_pages: Int!
var data: [User]?
}
class User: Decodable {
var id: Double!
var first_name: String!
var last_name: String!
var avatar: String!
}
Use:
import Alamofire
import AlamofireMapper
let url1 = "https://raw.githubusercontent.com/sua8051/AlamofireMapper/master/user1.json"
Alamofire.request(url1, method: .get
, parameters: nil, encoding: URLEncoding.default, headers: nil).responseObject { (response: DataResponse<UserResponse>) in
switch response.result {
case let .success(data):
dump(data)
case let .failure(error):
dump(error)
}
}

Automatic JSON serialization and deserialization of objects in Swift

I'm looking for a way to automatically serialize and deserialize class instances in Swift. Let's assume we have defined the following class …
class Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
… and Person instance:
let person = Person(firstName: "John", lastName: "Doe")
The JSON representation of person would be the following:
{
"firstName": "John",
"lastName": "Doe"
}
Now, here are my questions:
How can I serialize the person instance and get the above JSON without having to manually add all properties of the class to a dictionary which gets turned into JSON?
How can I deserialize the above JSON and get back an instantiated object that is statically typed to be of type Person? Again, I don't want to map the properties manually.
Here's how you'd do that in C# using Json.NET:
var person = new Person("John", "Doe");
string json = JsonConvert.SerializeObject(person);
// {"firstName":"John","lastName":"Doe"}
Person deserializedPerson = JsonConvert.DeserializeObject<Person>(json);
As shown in WWDC2017 # 24:48 (Swift 4), we will be able to use the Codable protocol. Example
public struct Person : Codable {
public let firstName:String
public let lastName:String
public let location:Location
}
To serialize
let payload: Data = try JSONEncoder().encode(person)
To deserialize
let anotherPerson = try JSONDecoder().decode(Person.self, from: payload)
Note that all properties must conform to the Codable protocol.
An alternative can be JSONCodable which is used by Swagger's code generator.
You could use EVReflection for that. You can use code like:
var person:Person = Person(json:jsonString)
or
var jsonString:String = person.toJsonString()
See the GitHub page for more detailed sample code. You only have to make EVObject the base class of your data objects. No mapping is needed (as long as the json keys are the same as the property names)
Update: Swift 4 has support for Codable which makes it almost as easy as EVReflection but with better performance. If you do want to use an easy contractor like above, then you could use this extension: Stuff/Codable
With Swift 4, you simply have to make your class conform to Codable (Encodable and Decodable protocols) in order to be able to perform JSON serialization and deserialization.
import Foundation
class Person: Codable {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
Usage #1 (encode a Person instance into a JSON string):
let person = Person(firstName: "John", lastName: "Doe")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // if necessary
let data = try! encoder.encode(person)
let jsonString = String(data: data, encoding: .utf8)!
print(jsonString)
/*
prints:
{
"firstName" : "John",
"lastName" : "Doe"
}
*/
Usage #2 (decode a JSON string into a Person instance):
let jsonString = """
{
"firstName" : "John",
"lastName" : "Doe"
}
"""
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
dump(person)
/*
prints:
▿ __lldb_expr_609.Person #0
- firstName: "John"
- lastName: "Doe"
*/
There is a Foundation class called NSJSONSerialization which can do conversion to and from JSON.
The method for converting from JSON to an object looks like this:
let jsonObject = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &error) as NSDictionary
Note that the first argument to this method is the JSON data, but not as a string object, instead as a NSData object (which is how you'll often times get JSON data anyway).
You most likely will want a factory method for your class that takes JSON data as an argument, makes use of this method and returns an initialize object of your class.
To inverse this process and create JSON data out of an object, you'll want to make use of dataWithJSONObject, in which you'll pass an object that can be converted into JSON and have an NSData? returned. Again, you'll probably want to create a helper method that requires no arguments as an instance method of your class.
As far as I know, the easiest way to handle this is to create a way to map your objects properties into a dictionary and pass that dictionary for turning your object into JSON data. Then when turning your JSON data into the object, expect a dictionary to be returned and reverse the mapping process. There may be an easier way though.
You can achieve this by using ObjectMapper library. It'll give you more control on variable names and the values and the prepared JSON. After adding this library extend the Mappable class and define mapping(map: Map) function.
For example
class User: Mappable {
var id: Int?
var name: String?
required init?(_ map: Map) {
}
// Mapping code
func mapping(map: Map) {
name <- map["name"]
id <- map["id"]
}
}
Use it like below
let user = Mapper<User>().map(JSONString)
First, create a Swift object like this
struct Person {
var firstName: String?;
var lastName: String?;
init() {
}
}
After that, serialize your JSON data you retrieved, using the built-in NSJSONSerialization and parse the values into the Person object.
var person = Person();
var error: NSError?;
var response: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(), error: &error);
if let personDictionary = response as? NSDictionary {
person.firstName = personDictionary["firstName"] as? String;
person.lastName = personDictionary["lastName"] as? String;
}
UPDATE:
Also please take a look at those libraries
Swift-JsonSerialiser
ROJSONParser
Take a look at NSKeyValueCoding.h, specifically setValuesForKeysWithDictionary. Once you deserialize the json into a NSDictionary, you can then create and initialize your object with that dictionary instance, no need to manually set values on the object. This will give you an idea of how the deserialization could work with json, but you will soon find out you need more control over deserialization process. This is why I implement a category on NSObject which allows fully controlled NSObject initialization with a dictionary during json deserialization, it basically enriches the object even further than setValuesForKeysWithDictionary can do. I also have a protocol used by the json deserializer, which allows the object being deserialized to control certain aspects, for example, if deserializing an NSArray of objects, it will ask that object what is the type name of the objects stored in the array.