I'm having to use a FoundationDB database for a project. It stores data as a key:value pair stored as Bytes. I want to store a JSON object mapped to a struct I have. I want to be able save the data as an encoded JSON object and then be able to recreate the JSON object by reading the value Bytes from the DB.
In my CreateRecord function I pass a JSON in a request and use it to create my Country object. I need to convert the type Data into Bytes to store it.
So far I have come up with this.
let data: Bytes = try JSONEncoder().encode(country).base64URLEncodedString()
Then, when I read the record from the database I need to be able to reverse the process to create my Country object from the Bytes that store the JSON.
let mycountry:Country = try decoder.decode(Country.self, from: Data(bytes: DBrecord.value) )
The struct is:
struct Country: Content {
var country_name: String
var timezone: String
var default_PickUp_location: String = ""
init(country_name: String, timezone:String, default_PickUp_location: String?) {
self.country_name = country_name
self.timezone = timezone
if default_PickUp_location != nil {
self.default_PickUp_location = default_PickUp_location!
}
}
}
And a sample JSON is:
{ "country_name" : "Denmark", "timezone" : "Europe\/Copenhagen", "default_pickup_location" : "Copenhagen" }
I cannot seem to be able to reverse the conversion. Any help please?
In your JSON, you use default_pickup_location and in your struct you use default_PickUp_location. You need to settle on one version.
To discover this, I used the following test route:
router.get("json")
{
request throws -> String in
let json = "{ \"country_name\" : \"Denmark\", \"timezone\" : \"Europe/Copenhagen\", \"default_pickup_location\" : \"Copenhagen\" }"
let encoder = try JSONDecoder().decode(Country.self, from: json)
return "It works"
}
It returned:
{"error":true,"reason":"Value required for key
'default_PickUp_location'."}
Where is the type "Bytes" declared, I don't recognize it as a SwiftLang or Foundation Type. I do recoginize Data(bytes: Array<UInt>)? I would guess it is an encoding Decoding type inconsitency. Also for simplifying your encoding and decoding to JSON with out mangling Camel Case in swift you may want to try.
struct Country: Content {
let countryName: String
let timezone: String
let defaultPickupLocation: String
enum CodingKeys: String, CodingKey {
case countryName = "country_name"
case timezone
case defaultPickupLocation = "default_pickup_location"
}
}
I would further recommend that you grab then encoders and decoders from the container for reasons of type consistency. If for example all of your JSON was going to be snake case you could change the encoders and decoders for your container and skip the coding keys enum from above.
var encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
You can get the encoder and decoder from the container as follows
let contentCoders: ContentCoders = try worker.make()
let jsonEncoder = try contentCoders.requireDataEncoder(for: .json) as? JSONEncoder
Related
I have some REST API, which returns JSON. This API is used by more than one service (mobile, web,...) and it returns JSON that has more fields inside than I need and some of those additional fields can be changed on weekly basis.
For example I have JSON like that:
{ "name": "Toyota Prius", "horsepower": 134, "mileage": 123241,
"manufactured": 2017, "location": "One city", "origin": "Japan",
"convert": true //more properties follows... }
In mobile app I would only need to decode fields: name, horsepower and manufactured date, the rest can be ignored. Also some fields like origin haven't existed before but was added few days ago.
Question is how to write bulletproof decoder that would not break if there are some changes in JSON that do not affect my model in app? Also I have no word in output JSON from server and I cannot affect it. I just get data and I have to work with what I get.
I have wrote some experimental code for playground (car isn't printed out):
import UIKit
struct Car: Codable
{
let name: String
let horsePower: Int
let selected: Bool //this is used as additional property inside my app to mark selected cars
let index: Int //this is used as additional property inside my app for some indexing purposes
enum CodingKeys: String, CodingKey
{
case name
case horsePower = "horsepower"
case selected
case index
}
init(from decoder: Decoder) throws
{
let values = try decoder.container(keyedBy: CodingKeys.self)
selected = false
index = 0
name = try values.decode(String.self, forKey: .name)
horsePower = try values.decode(Int.self, forKey: .horsePower)
}
}
let json = "{\"name\": \"Toyota Prius\",\"horsepower\": 134, \"convert\", true}"
let data = json.data(using: .utf8)
if let car = try? JSONDecoder().decode(Car.self, from: data!)
{
//does not print... :(
print(car)
}
I would like to get car printed in my example but mostly have a working proof code that will not break if JSON get changed.
Also is there a way to get an decoding error description somehow?
I know there are a lot of things in apple documentation, but mostly it is just too confusing to me and I couldn't find any useful examples for my problem.
First of all never try? JSONDecoder... , catch always the error and print it. DecodingErrors are extremely descriptive. They tell you exactly what is wrong and even where.
In your example you will get
"The given data was not valid JSON. ... No value for key in object around character 52."
which is the wrong comma (instead of a colon) after convert\"
To decode only specific keys declare the CodingKeys accordingly and delete the init method. selected and index are most likely supposed to be mutable so declare them as variable with a default value.
If the backend changes the JSON structure you'll get an error. The decoding process will break anyway regardless of the parsing API.
struct Car: Codable
{
let name: String
let horsePower: Int
let convert : Bool
var selected = false
var index = 0
enum CodingKeys: String, CodingKey {
case name, horsePower = "horsepower", convert
}
}
let json = """
{"name":"Toyota Prius","horsepower":134,"convert":true}
"""
let data = Data(json.utf8)
do {
let car = try JSONDecoder().decode(Car.self, from: data)
print(car)
} catch { print(error) }
I am using Xcode 10.1 and Swift 4.2. When i try to convert JSON response into Codable class it gives an error that Expected to decode Array<Any> but found a string/data instead.
My Actual JSON response is Like this from API .
{
"d": "[{\"Data\":{\"mcustomer\":[{\"slno\":1000000040.0,\"fstname\":null}]},\"Status\":true}]"
}
My Model is Like this
class MainData: Codable{
var d: [SubData]
}
class SubData : Codable {
var Data : Customer
var Status : Bool?
}
class Customer : Codable {
var mcustomer : [Detail]
}
class Detail : Codable {
var slno : Double?
var fstname : String?
}
And I am Decode this Model using JSONDecoder()
let decoder = JSONDecoder()
let deco = try decoder.decode(MainData.self, from: data)
but, I am unable to Decode this Json into My Model.
Your API is wrong. You array in json shouldn't have quotation marks around it. Otherwise you're declaring that value for key "d" is string
"[...]"
[...]
Suggestions:
Variables and constants should start with small capital letter. Otherwise for example your Data property would cause confusion with Data type. For renaming it while decoding you can use CodingKeys
If you don't need to encode your model, you can just implement Decodable protocol
You can use struct instead of class for your model
The top-level JSON object is a dictionary with the key "d" and a string value, representing another JSON object (sometimes called "nested JSON"). If the server API cannot be changed then the decoding must be done in two steps:
Decode the top-level dictionary.
Decode the JSON object from the string obtained in step one.
Together with Robert's advice about naming, CodingKeys and using structs it would look like this:
struct MainData: Codable {
let d: String
}
struct SubData : Codable {
let data : Customer
let status : Bool
enum CodingKeys: String, CodingKey {
case data = "Data"
case status = "Status"
}
}
struct Customer : Codable {
let mcustomer : [Detail]
}
struct Detail : Codable {
let slno : Double
let fstname : String?
}
do {
let mainData = try JSONDecoder().decode(MainData.self, from: data)
let subData = try JSONDecoder().decode([SubData].self, from: Data(mainData.d.utf8))
print(subData)
} catch {
print(error)
}
For your solution to work, the JSON reponse has to be following format
let json = """
{
"d": [
{
"Data": {
"mcustomer": [
{
"slno": 1000000040,
"fstname": null
}
]
},
"Status": true
}
]
}
"""
But, as you can see, the JSON response you are getting is quite different than you are expecting. Either you need to ask to change the response or you need to change your model.
Consider the next example:
import Foundation
class UDFrame: Codable {
var data:Data
init(data:Data) {
self.data = data
}
}
class Event: Codable {
var name:String
init(name:String) {
self.name = name
}
}
let encoder = JSONEncoder()
let event = Event(name: "eventName")
let serializedEvent = try encoder.encode(event)
let frame = UDFrame(data: serializedEvent)
let serializedFrame = try encoder.encode(frame)
print(String(data: serializedFrame, encoding: String.Encoding.utf8)!)
Result of the print statement is next: {"data":"eyJuYW1lIjoiZXZlbnROYW1lIn0="}.
My question is how to get "eventName" out of this drivel?
And, if possible, could you please explain why Data is serialized in such way by JSONEncoder, and what's the way to get initial data on another platform when such a JSON is given?
You can simply use JSONDecoder for decoding a JSON-encoded Data.
Data is simply base64encoded, so you just need to base64 decode it on another platform to get back the original data. However, there's no need to store a JSON-encoded object as a property of another object, you can simply use the JSON-encoded object.
I have
var contacts : [ContactsModel] = []
and
class ContactsModel: NSObject
{
var contactEmail : String?
var contactName : String?
var contactNumber : String?
var recordId : Int32?
var modifiedDate : String?
}
Now in contacts I'm having 6 values like
Now i want to convert contacts into JSON how can i ?
I tried
var jsonData: NSData?
do
{
jsonData = try NSJSONSerialization.dataWithJSONObject(contacts, options:NSJSONWritingOptions.PrettyPrinted)
} catch
{
jsonData = nil
}
let jsonDataLength = "\(jsonData!.length)"
But it crashes the app.
My issue is with manually converting in to a Dictionary one by one very time consuming it takes more than 5 minutes for 6000 records, So instead of that i want to convert directly model into JSON and send to server.
Your custom object can't be converted to JSON directly. NSJSONSerialization Class Reference says:
An object that may be converted to JSON must have the following properties:
The top level object is an NSArray or NSDictionary.
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity.
You might convert your object to a Dictionary manually or use some libraries like SwiftyJSON, JSONModel or Mantle.
Edit: Currently, with swift 4.0+, you can use Codable protocol to easily convert your objects to JSON. It's a native solution, no third party library needed. See Using JSON with Custom Types document from Apple.
If you just want a simple Swift object to JSON static function without any inheritance or dependencies to NSObject or NS-types directly. Check out:
https://github.com/peheje/JsonSerializerSwift
Full disclaimer. I made it. Simple use:
//Arrange your model classes
class Object {
var id: Int = 182371823
}
class Animal: Object {
var weight: Double = 2.5
var age: Int = 2
var name: String? = "An animal"
}
class Cat: Animal {
var fur: Bool = true
}
let m = Cat()
//Act
let json = JSONSerializer.toJson(m)
Currently supports standard types, optional standard types, arrays, arrays of nullables standard types, array of custom classes, inheritance, composition of custom objects.
You can use NSJSONSerialization for array when array contains only JSON encodable values (string, number, dictionary, array, nil)
first you need to create the JSON object then you can use it (your code is crashing because contact is not a JSON object!)
you can refere below link
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSJSONSerialization_Class/#//apple_ref/doc/uid/TP40010946-CH1-SW9
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.