I'm using Alamofire and SwiftyJSON to get and manage data from an API
After making my initial request I end up with nested collection of type JSON
According to SwiftyJSON I can loop through data like so
https://github.com/SwiftyJSON/SwiftyJSON#loop
for (key: String, subJson: JSON) in json {
//Do something you want
}
Again, according to SwiftyJSON I should be able to set new values like so:
https://github.com/SwiftyJSON/SwiftyJSON#setter
json["name"] = JSON("new-name")
I have a nested collection of data and I can dig in as deep as I want, but I'm unable to alter the object and set new key:value pair. How would I got about doing this in Swift?
Here's my code :
for (key: String, stop: JSON) in stops {
var physicalStops = stop["physicalStops"]
for (key: String, physicalStop: JSON) in physicalStops {
println("Prints out \(physicalStop) just fine")
// physicalStop["myNewkey"] = "Somevalue" // DOES NOT WORK (#lvalue is not identical to 'JSON)
// physicalStop["myNewkey"] = JSON("Somevalue") //SAME Story
}
}
Basically I'd like to keep the very same structure of the original JSON object, but add additional key:value on the second level nesting for each sub object.
First, you can use var in for-loop to make value modifiable inside the loop. However, JSON is struct so it behaves as a value type, so in your nested example, you have to reassign also the child JSON to the parent JSON otherwise it just modifies the value inside the loop but not in the original structure
var json: JSON = ["foo": ["amount": 2], "bar": ["amount": 3]]
for (key: String, var item: JSON) in json {
println("\(key) -> \(item)")
item["price"] = 10
json[key] = item
}
println(json)
Below is code that runs fine in Swift 2 playground and does not require Swifty:
var json: [String:AnyObject] = ["foo": ["amount": 2], "bar": ["amount": 3]]
for (key,item) in json {
print("\(key) -> \(item)")
let newItem = ["price": 10]
json[key] = newItem
}
print(json)
Related
The problem I am trying to solve is perfectly described by the following text got from this link:
For a concrete example of when this could be useful, consider an API that supports partial updates of objects. Using this API, a JSON object would be used to communicate a patch for some long-lived object. Any included property specifies that the corresponding value of the object should be updated, while the values for any omitted properties should remain unchanged. If any of the object’s properties are nullable, then a value of null being sent for a property is fundamentally different than a property that is missing, so these cases must be distinguished.
That post presents a solution but using the kotlinx.serialization library, however, I must use gson library for now.
So I am trying to implement my own solution as I didn't find anything that could suit my use case (please let me know if there is).
data class MyObject(
val fieldOne: OptionalProperty<String> = OptionalProperty.NotPresent,
val fieldTwo: OptionalProperty<String?> = OptionalProperty.NotPresent,
val fieldThree: OptionalProperty<Int> = OptionalProperty.NotPresent
)
fun main() {
val gson = GsonBuilder()
.registerTypeHierarchyAdapter(OptionalProperty::class.java, OptionalPropertyDeserializer())
.create()
val json1 = """{
"fieldOne": "some string",
"fieldTwo": "another string",
"fieldThree": 18
}
"""
println("json1 result object: ${gson.fromJson(json1, MyObject::class.java)}")
val json2 = """{
"fieldOne": "some string",
"fieldThree": 18
}
"""
println("json2 result object: ${gson.fromJson(json2, MyObject::class.java)}")
val json3 = """{
"fieldOne": "some string",
"fieldTwo": null,
"fieldThree": 18
}
"""
println("json3 result object: ${gson.fromJson(json3, MyObject::class.java)}")
}
sealed class OptionalProperty<out T> {
object NotPresent : OptionalProperty<Nothing>()
data class Present<T>(val value: T) : OptionalProperty<T>()
}
class OptionalPropertyDeserializer : JsonDeserializer<OptionalProperty<*>> {
private val gson: Gson = Gson()
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): OptionalProperty<*> {
println("Inside OptionalPropertyDeserializer.deserialize json:$json")
return when {
// Is it a JsonObject? Bingo!
json?.isJsonObject == true ||
json?.isJsonPrimitive == true-> {
// Let's try to extract the type in order
// to deserialize this object
val parameterizedType = typeOfT as ParameterizedType
// Returns an Present with the value deserialized
return OptionalProperty.Present(
context?.deserialize<Any>(
json,
parameterizedType.actualTypeArguments[0]
)!!
)
}
// Wow, is it an array of objects?
json?.isJsonArray == true -> {
// First, let's try to get the array type
val parameterizedType = typeOfT as ParameterizedType
// check if the array contains a generic type too,
// for example, List<Result<T, E>>
if (parameterizedType.actualTypeArguments[0] is WildcardType) {
// In case of yes, let's try to get the type from the
// wildcard type (*)
val internalListType = (parameterizedType.actualTypeArguments[0] as WildcardType).upperBounds[0] as ParameterizedType
// Deserialize the array with the base type Any
// It will give us an array full of linkedTreeMaps (the json)
val arr = context?.deserialize<Any>(json, parameterizedType.actualTypeArguments[0]) as ArrayList<*>
// Iterate the array and
// this time, try to deserialize each member with the discovered
// wildcard type and create new array with these values
val result = arr.map { linkedTreeMap ->
val jsonElement = gson.toJsonTree(linkedTreeMap as LinkedTreeMap<*, *>).asJsonObject
return#map context.deserialize<Any>(jsonElement, internalListType.actualTypeArguments[0])
}
// Return the result inside the Ok state
return OptionalProperty.Present(result)
} else {
// Fortunately it is a simple list, like Array<String>
// Just get the type as with a JsonObject and return an Ok
return OptionalProperty.Present(
context?.deserialize<Any>(
json,
parameterizedType.actualTypeArguments[0]
)!!
)
}
}
// It is not a JsonObject or JsonArray
// Let's returns the default state NotPresent.
else -> OptionalProperty.NotPresent
}
}
}
I got most of the code for the custom deserializer from here.
This is the output when I run the main function:
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:"another string"
Inside OptionalPropertyDeserializer.deserialize json:18
json1 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=Present(value=another string), fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json2 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=my.package.OptionalProperty$NotPresent#573fd745, fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json3 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=null, fieldThree=Present(value=18))
I am testing the different options for the fieldTwo and it is almost fully working, with the exception of the 3rd json, where I would expect that fieldTwo should be fieldTwo=Present(value=null) instead of fieldTwo=null.
And I see that in this situation, the custom deserializer is not even called for fieldTwo.
Can anyone spot what I am missing here? Any tip would be very appreciated!
I ended giving up of gson and move to moshi.
I implemented this behavior based on the solution presented in this comment.
I have a simple JSON object:
{
"values": {
"a":"",
"b":"",
"c":"",
"d":"",
"e":""
}
}
and I want to decode it to a Swift struct in such a way, that I can later be able to iterate over the keys in values in the exact same order as I receive the JSON object.
Is this possible in Swift?
My try below:
let json = "{ \"values\": { \"a\":\"\", \"b\":\"\", \"c\":\"\", \"d\":\"\", \"e\":\"\" } }"
struct JSS: Codable {
var values: [String: String?]?
}
let data = json.data(using: .utf8)
do {
let decoder = JSONDecoder()
let jss = try decoder.decode(JSS.self, from: data!)
jss.values?.map { print("\($0.key)") }
}
catch {
}
will print:
b
e
a
d
c
This is not a Swift limitation per se. Both Swift and JSON Dictionaries are unordered. The JSON format does not guarantee key ordering, and as such, does not require parsers to preserve the order.
If you need an ordered collection, you'd be better off with returning an array of key-value pairs in the JSON:
{
"values": [
{"a" : ""},
{"b" : ""},
{"c" : ""},
{"d" : ""},
{"e" : ""}
]
}
And then store the keys in the right order to be able to iterate over them as you wish.
The following does not work so you don't waste time trying:
Back to the old fashion way, use JSONSerialization.jsonObject(with: Data, options: JSONSerialization.ReadingOptions) -> Any.
It does keep order..... UNTIL you cast json?["values"] into a [String: Any]. At this time what Cezar says in the above answer enters the scene: dictionaries are unordered.
The following screenshot shows that, until json?["values"] is an Any, the order is kept in the string description.
I have a SQL Database on Azure and I would like to synchronize it with Realm, for my iOS App (in Swift)
For that, I have created a REST API which generates a JSON and now I would like to integrate this JSON in Realm.
To do that, I have tried to follow the explanation on Realm Documentation, so now I have :
Realm Table :
class tbl_test: Object {
dynamic var id:Int = 0
dynamic var name:String = ""
override class func primaryKey() -> String? {
return "id"
}
}
Swift Code :
let realm = try! Realm()
let stringTxt:String = "[{\"id\": 1, \"name\": \"My Name\"}]"
var myData = NSData()
if let dataFromString = stringTxt.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
let jsonData = JSON(data: dataFromString)
if let encryptedData:NSData = try! jsonData.rawData() {
myData = encryptedData
}
}
try! realm.write {
let json = try! NSJSONSerialization.JSONObjectWithData(myData, options: NSJSONReadingOptions())
realm.create(tbl_test.self, value: json, update: true)
}
I use SwiftyJSON to convert my string to JSON.
When I run the program, I have this error message :
[__NSCFDictionary longLongValue]: unrecognized selector sent to
instance 0x7fdcc8785820 2016-07-06 10:25:30.090
mydrawing[9436:2732447] *** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFDictionary
longLongValue]: unrecognized selector sent to instance 0x7fdcc8785820'
Is it a good way to import JSON in Realm ? There is no official way according to what I have found, but this method should work...
The problem you're facing is that the structure of the data you're passing to Realm.create(_:value:update:) doesn't match what the method expects. It expects either a dictionary with keys corresponding to the managed properties on your model type, or an array with one element for each managed property.
After deserializing the JSON data, json looks like so:
(
{
id = 1;
name = "My Name";
}
)
That is an array containing a single element that is an dictionary. When you pass this array to Realm.create(_:value:update:), Realm expects the first element of the array to be the value to use as the id property on your tbl_test type.
I suspect that what you mean to do is to call Realm.create on each of the elements of the array in turn, instead of calling it on the array itself.
I have this subJSON["guestpics"] as JSON data from SwiftyJSON.
When I print(subJSON["guestpics"]) I have this:
[
"/images\/profile_pic\/1.jpg",
"/images\/profile_pic\/2.jpg",
"/images\/profile_pic\/3.jpg"
]
How can I convert this to an array ?
for (_, subJSON): (String, JSON) in json[0]["data"] {
print(subJSON["guestpics"])
}
SwiftyJSON has already parsed your JSON data and prepared typed objects.
If the key subJSON["guestpics"] contains an array, then use SwiftyJSON's optional getter .array to get it:
if let guestPicsArray = subJSON["guestpics"].array {
// here "guestPicsArray" is your array
}
Why not directly storing the value of the key in the array? as it look like array
Does it show any error/crash when you are trying to store ?
if let arrGuest = subJSON["guestpics"] as? Array<String> {
}
Or if you are more familiar with Objective-c
if let arrGuest = arr as? NSArray {
}
you can get the array in the arrGuest object
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.