can a JSON be deserialized into an existing object? - ios

We have some classes that conform to the codable interface.
Now we have a requirement similar to deep copy, but we need to pass the value into an existing object.
For example:
class A: Codable{
var a1: Int
var a2: String
....aN: Codable
enum CodingKeys: String, CodingKey {
case a1
case a2
case a3
...
case aN
}
}
let a = A()
We get a new JSON, and we can easily convert it into A object through decode.
But now we don't want the a's memory address to change. We want its ARC to remain in its current state.
That is, when deserializing, we want to write the content directly to the object a.
We hope to be more automatic. We can traverse the processing according to allkeys. We don't have to write each one.
a[keyPath: \.a1] = newObj[keyPath: \.a1]
a[keyPath: \.a2] = newObj[keyPath: \.a2]
...
a[keyPath: \.aN] = newObj[keyPath: \.aN]
or
a.a1 = newObj.a1
a.a2 = newObj.a2
...
a.aN = newObj.aN

Deserializing will always result in creating a new object. So, I guess, the most viable (if not the only) option is to manually compare all properties and update the existing object accordingly.
You can write helper methods in your existing classes for convenience. Probably, something like this:
import Foundation
class A: Codable {
var a1: String
init(a1: String) {
self.a1 = a1
}
}
extension A {
func updateWith(_ another: A) {
if a1 != another.a1 {
a1 = another.a1
}
}
}
let a = A(a1: "a1")
let anotherString = "{\"a1\":\"new a1\"}"
if let anotherData = anotherString.data(using: .utf8),
let another = try? JSONDecoder().decode(A.self, from: anotherData) {
a.updateWith(another)
}
print(a.a1) // "new a1"

Related

what is the best way to save json data in swift model

I previously made a post about how to save json data in a model here .
Good now I am developing a project on iOS with swift, and I face the following problem, it happens that sometimes the database administrators change the names of the columns constantly, I only consume services with the help of Alamofire, to save data in models, use camal case and snake case, At the moment everything is fine but I would like to know what is the best way to save json data in a swift model, in my experience with Android I used retrofit with #Serializename and it worked great because if the json attribute of the service it was modified I only had to update a line of code and my variable could be kept the same, this helped me maintain a better order and it made it scalable.
In some cases the json comes to me.
{
"price": "385.000000",
"nameusser": null,
"favorite": 43,
"short_nameProduct": "Génifique Repair Sc",
"description_product": "Génifique repair sc es la crema de noche antiedad de lancôme. Despiértese con una piel fresca y rejuvenecida con nuestra crema de noche.",
"alt": null,
"photo": "https://url/020021000112-1.png"
}
in swift it would generate my model in the following way.
struct Product : Codable {
let price : String?
let nameusser : String?
let favorite : Int
let shortNameProduct : [Product]
let description : [Galery]
let alt : Product
let success : Bool
}
The problem here is that my variables must fit the json I get to use the JSONDecoder() and the convertFromSnakeCase, I can not define them myself.
while in java android I just have to do it like that.
#SerializedName("price")
private String price;
#SerializedName("nameusser")
private String name;
#SerializedName("favorite")
private Int favorite;
#SerializedName("short_nameProduct")
private String shortName;
#SerializedName("description_product")
private String descriptionProduct;
#SerializedName("altitude")
private String altitude;
#SerializedName("photo")
private String photo;
I just have to create the get and set and I would be ready to use the model.
I need to know how to do in swift the same, maybe a library that helps me store data json in the same way that I do in android.
Any comment would be very appreciated.
The best way is to use the Coding Keys:
struct Product : Codable {
let price : String?
let nameusser : String?
let favorite : Int
let shortNameProduct : [Product]
let description : [Galery]
let alt : Product
let success : Bool
enum CodingKeys: String, CodingKey {
case price = "price"
case nameusser = "nameusser"
case favorite = "favorite"
case shortNameProduct = "short_nameProduct"
case description = "description_product"
case alt = "alt"
case success = "success"
}
}
The name of the enum case has to match the property name on the struct. This way you can define whatever keys you want without having to write any custom encoding or decoding code.
Feel free to use my gist here:
Storage
You can use it like this:
let fileName = "Product.json"
extension Product {
func store() {
Storage.store(self, to: .documents, as: fileName)
}
static func retrieve() -> Product? {
guard let product = Storage.retrieve(fileName, from: .documents, as: Product.self) else {
return nil
}
return product
}
}
With Alamofire you will get a key-value, an Dictionary<String,Any> or [String:Any]
So you can do the following with your dictionary:
var myProduct : Product?
if let price = myDictionary["price"] as? String{
myProduct.price = price
}
With that example you can create a method to mapping the entire JSON into your struct. Maybe if you want to make it more scalable, you can create an enum with String raw values and use it as the key for the dictionary, some like:
enum productProperty : String{
case Price = "price"
}
var myProduct : Product?
if let price = myDictionary[productProperty.Price] as? String{
myProduct.price = price
}
And maybe create a more complex class to iterate trough the dictionary and check the key using the enum, but that implementation depends of your own skills.
Edit1:
To use't with alamofire, you need to get the jsonResponse of the request, some like that:
.request(yourURL, method: .get, parameters: yourParameter).responseJSON(options: .allowFragments , completionHandler: { response in
if let object = response.result.value as? Dictionary<String,Any> {
yourMethodToSave(object)
}
})
and inside yourMethodToSave(_ object: Dictionary<String,Any>) you need to put the logic above.
Ps: The #sendtobo answer have the enum example that i tell that you can use to a more scalable mapping for your object

Swift: How to access variable element of an enum?

For hours I've been struggeling with getting an variable element of an enum.
The "Swifticons" - pod provides me with the following enum:
public enum WeatherType: Int {
static var count: Int {
return weatherIcons.count
}
public var text: String? {
return weatherIcons[rawValue]
}
case alien, barometer, celsius, owm300, owm301, owm302, and200moreOfTheseNames
}
private let weatherIcons = ["\u{f075}", "\u{f079}", and202moreOfTheseFontCharacters]
From an external API (openWeatherMap.org) I just get an weather code (let's say "300") - and I want to access Icon "owm300".
But how do I access this element of the enum without knowing the rawValue (which would be - say - 198)?
Here's the plan:
We need to enumerate all of the enum cases. We'll do that by iterating over raw values (luckily, WeatherType is backed by Int).
We will store lazily initialized dictionary that maps String to WeatherType.
And finally, we declare a static function that returns an optional WeatherType? because we can encounter an unknown value.
Here's the code:
extension WeatherType {
// just some little convenience
private typealias W = WeatherType
// 1. define the sequence of all cases
private static func allCases() -> AnySequence<W> {
return AnySequence { () -> AnyIterator<W> in
var raw = 0
return AnyIterator {
// Iterates while raw value can be converted to an enum case
if let next = W(rawValue: raw) {
raw += 1
return next
}
return nil
}
}
}
// 2. Static properties are lazy so we'll use them to store the dictionary with String to WeatherType mapping
private static let typeMap = W.allCases().reduce([String: W]()) { acc, next in
var acc = acc
acc[String(describing: next)] = next
return acc
}
// 3. Declare the mapping function
static func from(string: String) -> WeatherType? {
return W.typeMap[string]
}
}
Here's a little test:
let str = "301"
let type = WeatherType.from(string: "owm\(str)")
print(type == .owm301)
One of the easiest way I can think of is create some kind of mapping dictionary, where you would keep track of weather response code and WeatherType that it maps to like so,
let weatherCodeMapping: [Int: WeatherType] = [300: .owm300,
301: .owm301,
302: .owm302]
With this in place, you dont need to know any specific rawValue, you can simply get code by,
let weatherType = weatherCodeMapping[weatherCode]
And then create some other mapping for your image based on the weatherType.
let weatherIcon = weatherIconMapping[weatherType]
or create a single mapping directly from weather code to icon.
Swift doesn't currently have enumerable sequences of enum cases. One option that you have is to copy the list of icon names, then search for your icon's name, and use that index as the enum's rawValue:
let weatherIcons = [...]
let iconName = "owm300"
let possibleIconIndex = weatherIcons.index {
$0.caseInsensitiveCompare(iconName) == .orderedSame
}
if let iconIndex = possibleIconIndex {
let weatherIcon = WeatherIcon(rawValue: iconIndex)!
// ...
} else {
// graceful fallback for when the weather icon is missing
}
Of course, you need to figure out your own mapping between the data you get from the service and enum names, but that could be as simple as "owm\(weatherCode)".
When Swift 4.2 lands, you will be able to make your enums conform to a new protocol called CaseIterable. Enums that conform to it get a synthesized implementation of an allCases static variable. You will then be able to use that enumeration to build a string-to-enum dictionary automatically:
let nameToEnum = WeatherIcon.allCases.map { (String($0), $0) }
let mapping = Dictionary(uniqueKeysWithValues: nameToEnum)
That will however require WeatherIcon to be declared with the CaseEnumerable conformance, as adding it with an extension has no effect.

How can I properly copy objects from one Realm object to another object

Based on the following code I would like to be able to create a new ItemList from an existing one. In other words I have an ItemList called First List and I want to create a new ItemList, call it Second List and fill it with the Items from First List.
The way I have it right now is that it creates the Second List as expected, the Items from the First List show in Second List but what doesn't work is when I want to delete only the Items from First List, it deletes Items from both lists. I guess I'm not truly copying the items.
So the question is, how can I copy Items from First List to Second List?
Object Models:
class ItemList: Object {
dynamic var listName = ""
dynamic var createdAt = NSDate()
let items = List<Item>()
}
class Item:Object{
dynamic var productName:String = ""
dynamic var createdAt = NSDate()
}
Code to create Second List from First List
This Works fine, it creates Second List and adds the items from First List but I don't think I'm making copies just showing them in Second List.
let newList = ItemList()
newList.listName = "Second List"
if let selectedList = realm.objects(ItemList.self).filter("listName = %#", "First List").first{
let itemsFromFirstList = selectedList.items
newList.items.append(objectsIn:itemsFromFirstList)
}
try! realm.write {
realm.add(newList)
}
This code is supposed to delete only the items from First List
This actually deletes items from both First List and Second List
let listToDelete = realm.objects(ItemList.self).filter("listName = %#", "First List").first
try! realm.write {
for item in (listToDelete?.items)! {
realm.delete(realm.objects(Item.self).filter("productName = %#", item.productName).first!)
}
}
What you want to do is use:
for record in postsDB.objects(PostModel.self) {
if !combinedDB.objects(PostModel.self).filter("postId == \(record.parentId)").isEmpty {
combinedDB.create(PostModel.self, value: record, update: false)
}
}
The create method is inherited from Object. It tells the target to create a new object. Use true if you want it to look to see if there is already a record there, and update it if there is.
PostModel is the Object type, record is what you want copied.
Edit: I added the if statement to provide more context. You didn't show your class definitions, so I was guessing. This is a working example. I ask for a set of records from DatabaseA and copy it to DatabaseB (postsDB to combinedDB).
So if the type of the object you're trying to insert is a List, I'd recommend you define a subclass of Object, and have at least the list you need as a property.
class TagList: Object {
dynamic var tag = ""
var list = List<PostModel>()
override class func primaryKey() -> String? {
return "tag"
}
}
Full working example illustrating: creating new objects, copying all objects to a second list, deleting from second list after copying, adding to first list (which didn't get anything deleted from it.
import Foundation
import RealmSwift
class Letter: Object {
dynamic var letter = "a"
}
class Letters: Object {
var letterList = List<Letter>()
}
class ListExample {
let listRealmStore = try! Realm() // swiftlint:disable:this force_try
func testThis() {
print(Realm.Configuration.defaultConfiguration.fileURL!)
listRealmStore.beginWrite()
addSingleItems() // add 3 objects to the DB
let firstList = Letters()
let allObjects = listRealmStore.objects(Letter.self)
for item in allObjects {
firstList.letterList.append(item)
}
let secondList = Letters()
let itemsToCopy = firstList.letterList
for item in itemsToCopy {
let obj = listRealmStore.create(Letter.self)
obj.letter = item.letter
secondList.letterList.append(obj)
}
let third = Letter()
third.letter = "Z"
listRealmStore.add(third)
firstList.letterList.append(third)
secondList.letterList.removeLast()
do {
try listRealmStore.commitWrite()
} catch let error {
print("couldn't commit db writes: \(error.localizedDescription)")
}
print("list one:\n\(firstList)")
print("list two:\n\(secondList)")
}
func addSingleItems() {
for letter in ["a", "b", "c"] {
let objectToInsert = Letter()
objectToInsert.letter = letter
listRealmStore.add(objectToInsert)
}
}
}
Results in:
list one:
Letters {
letterList = List<Letter> (
[0] Letter {
letter = a;
},
[1] Letter {
letter = b;
},
[2] Letter {
letter = c;
},
[3] Letter {
letter = Z;
}
);
}
list two:
Letters {
letterList = List<Letter> (
[0] Letter {
letter = a;
},
[1] Letter {
letter = b;
}
);
}
Are you really trying to create copies of your items, or do you just want to be able to remove them from lists independently?
When you do:
newList.items.append(objectsIn: itemsFromFirstList)
you end up with the same objects being in both lists. List<T> just stores references to objects that live within the Realm. Appending an object to a List just references the existing object, it doesn't copy the object.
When you call Realm.delete(_:) you remove that object entirely from the Realm, not just from a single list that it is a member of. To remove an object from a List, you should instead use List.remove(objectAtIndex:).
One part the solution you are looking for could be like this, make copy objects in the list, or you can just use this idea to clone whole list it self:
Previously answered here
As of now, Dec 2020, there is not proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Note")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.

Iterate over static properties of a struct

Is there a simple way to iterate over all of the properties of a struct? The approach for non-static properties that I am familiar with is using Mirror, but this returns an empty array when the struct contains only static properties. Here's an example of what I am trying to achieve:
struct Tree {
static let bark = "Bark"
static let roots = "Roots"
}
let treeParts = [String]()
// insert code here to make treeParts = ["Bark", "Roots"]
Since I also have an interest of how to do this I made the example below. Why not just create the struct with non static properties plus a static instance variable which makes the struct a singleton. The following code sample details an example use case for mapping values of a Person object to a JSON dictionary using the REST API's naming semantics. The property names of PersonJSONKeys have to match the property names of Person.
The code for the allProperties() function comes from How to loop over struct properties in Swift?. You can modify this function easily to only return the values of a structs properties.
struct PersonJSONKeys: PropertyLoopable {
static let instance: PersonJSONKeys = PersonJSONKeys()
private init() {}
let name = "name"
let firstName = "first_name"
let age = "age"
}
struct Person: PropertyLoopable {
let name = "Doe"
let firstName = "John"
let age = "18"
}
let person = Person()
let personProperties = person.allProperties()
var personJSON: [String:Any] = [:]
for jsonProperty in PersonJSONKeys.instance.allProperties() {
let propertyName = jsonProperty.key
let jsonKey = jsonProperty.value as! String
personJSON[jsonKey] = personProperties[propertyName]
}
Since the Struct is now a singleton all of its properties will be initialised only once and the thread safety is given by its static instance variable.
I was also looking for this and ended up using an enum. It's a good thing if you only have a list of static values (but enums also come with some limitations, e.g. no extensions...):
enum Tree: String, CaseIterable {
case bark = "Bark"
case roots = "Roots"
}
let allTrees = Tree.allCases.map{ $0.rawValue }
print(allTrees) // => ["Bark", "Roots"]

How to achieve object merging through generics in swift?

Imagine I have a class Number:
class Number {
var val: Double?
}
and have two instances of that class, A and B.
Now imagine I want to merge Binto Athrough a statement like
merge(B, into: A)
Now of course I could write the function like this:
func merge(from: Number, into: Number){
into.val = from.val
}
But that isn't reusable at all. Is there a way I could write a generic merge class?
UPDATE: Although some of the answers offer good and viable solutions, none of them are "generic" enough (generic here is meant in a non-technical way).So looking at the answers, I got some inspiration, and here is the solution I am now considering: make Number a NSObject subclass and declare all the properties that can be merged as dynamic. For example:
class Number: NSObject {
//Put the required init and initWithCoder: here
dynamic var val: Double?
}
Then declaring a protocol that mergeable classes must respect
protocol Mergeable: class {
var mergeablePropertyKeys:[String] {get}
}
And then declaring a global function that performs a merge:
func merge<U: Mergeable, Mergeable where U.Type == V.Type>(from: U, into:V){
for property in U.mergeablePropertyKeys {
V.setValue(U.valueForKey(property), property)
}
}
And I know that this will not work because the arguments to merge are not necessarily NSObjects.
How can I make sure that the arguments to merge are both NSObjects?
Can avoid having to specify the names of all my mergeable values by simply obtaining a list of my object's dynamic values?
It sounds like what you want is a generic function that uses reflection to merge properties. Reflection is limited in Swift, but it is doable using the MirrorType. I have used this method before to build a generic json parser in swift - you could do something similar but instead of parsing a json dictionary to properties map your two object's properties.
An example of using reflection to do this in swift:
func merge<T>(itemToMerge:T) {
let mirrorSelf = Mirror(reflecting: self)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
This assumes the object defining the merge method is a subclass of NSObject (so it can take advantage of NSKeyValueCoding). You could also make this a static method that could merge any 2 objects of any NSObject type:
static func merge<T1: NSObject, T2: NSObject>(itemChanging:T1, itemToMerge:T2) {
let mirrorSelf = Mirror(reflecting: itemChanging)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
Im not sure what you are expecting but there is generic solution:
class Number<T> {
var val: T?
}
protocol Merge {
func merge(from: Self, into: Self)
}
extension Number: Merge {
func merge(from: Number, into: Number) {
into.val = from.val
}
}
Protocol
Lets define a HasValue protocol (available only for classes) like this
protocol HasValue: class {
typealias T
var val: T? { get set }
}
Merge
Now we can define a generic function
func merge<U: HasValue, V:HasValue where U.T == V.T>(from: U, into:V) {
into.val = from.val
}
The constraints in the function signature do guarantee that
Both params do conform to HasValue (therefore are classes)
val types for both params are equals
Scenario 1: params have the same type
class Number: HasValue {
var val: Double?
}
let one = Number()
one.val = 1
let two = Number()
two.val = 2
merge(one, into: two)
print(two.val) // Optional(1.0)
Scenario 2: params have different types but their values have the same type
I did not constrain the 2 params of Merge to having the same type, I am only checking that the val properties of the 2 params must have the same type.
So we could also merge different instances of different classes having val of the same type like
class Phone: HasValue {
var val: Int?
}
class Computer: HasValue {
var val: Int?
}
let iPhone = Phone()
iPhone.val = 10
let iMac = Computer()
iMac.val = 9
merge(iPhone, into: iMac)
print(iMac.val) // Optional(10)
Scenario 3: params have generic types
class Box<S>: HasValue {
var val: S?
}
let boxOfString = Box<String>()
boxOfString.val = "hello world"
let boxOfInt = Box<Int>()
boxOfInt.val = 12
merge(boxOfString, into: boxOfInt) // << compile error
let boxOfWords = Box<String>()
boxOfWords.val = "What a wonderful world"
merge(boxOfString, into: boxOfWords)
print(boxOfWords.val) // Optional("hello world")

Resources