Mapping Realm objects to a Dict via Swift map - swift

I have two (Realm.io) objects like this:
class Parent: Object {
var title = “”
var children = List<Child>()
var dict: [String: Any] {
print(“Dict 1”)
return ([“title”: self.title, “children”: self.children.map{ $0.dict }])
}
}
class Child: Object {
var name = “”
var dict: [String: Any] {
print(“Dict 2”)
return ([“name”: self.name])
}
}
I’m trying to convert them both to a dictionary in one swoop like this:
let parent_dict = parent.dict
It works fine for “title” and other such Parent attributes, but never actually goes to map the Child class object.
The output for the above example would be just: Dict 1

Be sure you add all the objects to the realm first. The following code works (try it in a playground inside your project):
import RealmSwift
class Parent: Object {
dynamic var title = ""
var children = List<Child>()
var dict: [String: Any] {
print("Dict 1")
return (["title": self.title, "children": self.children.map{ $0.dict }])
}
}
class Child: Object {
dynamic var name = ""
var dict: [String: Any] {
print("Dict 2")
return (["name": self.name])
}
}
let p1 = Parent(); p1.title = "John"
let c1 = Child(); c1.name = "Pam"
let c2 = Child(); c2.name = "Andrew"
let c3 = Child(); c3.name = "Barrack"
let realm = try! Realm()
try! realm.write {
realm.add(p1)
realm.add([c1, c2, c3])
p1.children.append(objectsIn: [c1, c2, c3])
}
print(p1.dict)
After running the code the console will read:
Dict 1
["title": "John", "children": Swift.LazyMapRandomAccessCollection<RealmSwift.List<Child>, Swift.Dictionary<Swift.String, Any>>(_base: List<Child> (
[0] Child {
name = Pam;
},
[1] Child {
name = Andrew;
},
[2] Child {
name = Barrack;
}
), _transform: (Function))]

Related

how to get single variable name from struct

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

Multiple Realm objects to JSON

I am trying to convert Realm Object into JSON. My version is working but not if you want to put multiple objects into JSON. So my question is, how should you add multiple Realm Objects into JSON?
Something like that:
{
"Users": [
{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}
],
"Posts": [
{"id": "1","title": "hey"},{"id": "2","title": "hey2"},{"id": "3","title": "hey3"}
]
}
This is what I am doing right now:
func getRealmJSON(name: String, realmObject: Object, realmType: Any) -> String {
do {
let realm = try Realm()
let table = realm.objects(realmType as! Object.Type)
if table.count == 0 {return "Empty Table"}
let mirrored_object = Mirror(reflecting: realmObject)
var properties = [String]()
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String! {
properties.append(property_name)
}
}
var jsonObject = "{\"\(name)\": ["
for i in 1...table.count {
var str = "{"
var insideStr = String()
for property in properties {
let filteredTable = table.value(forKey: property) as! [Any]
insideStr += "\"\(property)\": \"\(filteredTable[i - 1])\","
}
let index = insideStr.characters.index(insideStr.startIndex, offsetBy: (insideStr.count - 2))
insideStr = String(insideStr[...index])
str += "\(insideStr)},"
jsonObject.append(str)
}
let index = jsonObject.characters.index(jsonObject.startIndex, offsetBy: (jsonObject.count - 2))
jsonObject = "\(String(jsonObject[...index]))]}"
return jsonObject
}catch let error { print("\(error)") }
return "Problem reading Realm"
}
Above function does like that, which is good for only one object:
{"Users": [{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}]}
Like this I call it out:
let users = getRealmJSON(name: "Users", realmObject: Users(), realmType: Users.self)
let posts = getRealmJSON(name: "Posts", realmObject: Posts(), realmType: Posts.self)
And I tried to attach them.
Can anybody please lead me to the right track?
You can use data models to encode/decode your db data:
For example you have
class UserEntity: Object {
#objc dynamic var id: String = ""
#objc dynamic var createdAt: Date = Date()
#objc private dynamic var addressEntities = List<AddressEntity>()
var addresses: [Address] {
get {
return addressEntities.map { Address(entity: $0) }
}
set {
addressEntities.removeAll()
let newEntities = newValue.map { AddressEntity(address: $0) }
addressEntities.append(objectsIn: newEntities)
}
}
}
Here you hide addressEntities with private and declare addresses var with Address struct type to map entities into proper values;
And then use
struct User: Codable {
let id: String
let createdAt: Date
let addresses: [Address]
}
And then encode User struct any way you want

Merge objects of the same type

Say I have a struct Coin
struct Coin {
var value: Float?
var country: String?
var color: String?
}
I have two instances of a Coin; we'll call them coinA and coinB.
let coinA = Coin()
coinA.value = nil
coinA.country = "USA"
coinA.color = "silver"
let coinB = Coin()
coinB.value = 50.0
Now, I want to merge the values of coinB into coinA. So the result would be coinA whose values would result in:
country = "USA"
color = "silver"
value = 50.0
I am able to accomplish this with Dictionary objects using the merge() function. However, I am unsure how to accomplish this using custom Swift objects. Is there a way?
Update
Here's how I've gotten it to work with dictionaries:
var originalDict = ["A": 1, "B": 2]
var newDict = ["B": 69, "C": 3]
originalDict.merge(newDict) { (_, new) in new }
//originalDict = ["A": 1, "B": 69, "C": 3]
And I will further clarify, in this function if the newDict does not have keys that the originalDict, the originalDict maintains them.
Ultimately, the most efficient way in the fewest lines of code is probably exactly what you'd expect:
extension Coin {
func merge(with: Coin) -> Coin {
var new = Coin()
new.value = value ?? with.value
new.country = country ?? with.country
new.color = color ?? with.color
return new
}
}
let coinC = coinA.merge(with: coinB)
Note that in the above scenario, the resulting value will always be coinA's, and will only be coinB's if coinA's value for a given key is nil. Whenever you change, add, or delete a property on Coin, you'll have to update this method, too. However, if you care more about future-proofing against property changes and don't care as much about writing more code and juggling data around into different types, you could have some fun with Codable:
struct Coin: Codable {
var value: Float?
var country: String?
var color: String?
func merge(with: Coin, uniquingKeysWith conflictResolver: (Any, Any) throws -> Any) throws -> Coin {
let encoder = JSONEncoder()
let selfData = try encoder.encode(self)
let withData = try encoder.encode(with)
var selfDict = try JSONSerialization.jsonObject(with: selfData) as! [String: Any]
let withDict = try JSONSerialization.jsonObject(with: withData) as! [String: Any]
try selfDict.merge(withDict, uniquingKeysWith: conflictResolver)
let final = try JSONSerialization.data(withJSONObject: selfDict)
return try JSONDecoder().decode(Coin.self, from: final)
}
}
With that solution, you can call merge on your struct like you would any dictionary, though note that it returns a new instance of Coin instead of mutating the current one:
let coinC = try coinA.merge(with: coinB) { (_, b) in b }
I thought it would be interesting to show a solution based on Swift key paths. This allows us to loop somewhat agnostically through the properties — that is, we do not have to hard-code their names in a series of successive statements:
struct Coin {
var value: Float?
var country: String?
var color: String?
}
let c1 = Coin(value:20, country:nil, color:"red")
let c2 = Coin(value:nil, country:"Uganda", color:nil)
var c3 = Coin(value:nil, country:nil, color:nil)
// ok, here we go
let arr = [\Coin.value, \Coin.country, \Coin.color]
for k in arr {
if let kk = k as? WritableKeyPath<Coin, Optional<Float>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
} else if let kk = k as? WritableKeyPath<Coin, Optional<String>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
}
}
print(c3) // Coin(value: Optional(20.0), country: Optional("Uganda"), color: Optional("red"))
There are unfortunate features of key paths that require us to cast down from the array element explicitly to any possible real key path type, but it still has a certain elegance.
If you're willing to make the merge function specific to Coin, you can just use the coalesce operator like so:
struct Coin {
var value: Float?
var country: String?
var color: String?
func merge(_ other: Coin) -> Coin {
return Coin(value: other.value ?? self.value, country: other.country ?? self.country, color: other.color ?? self.color)
}
}
let coinC = coinA.merge(coinB)
This will return a new Coin using the values from coinB, and filling in any nils with those from coinA.
If your goal is to change coin A what you need is a mutating method. Note that structures are not like classes. If you would like to change its properties you need to declare your coin as variable. Note that none of your examples would compile if you declare your coins as constants:
struct Coin {
var value: Float?
var country: String?
var color: String?
mutating func merge(_ coin: Coin) {
value = value ?? coin.value
country = country ?? coin.country
color = color ?? coin.color
}
init(value: Float? = nil, country: String? = nil, color: String? = nil) {
self.value = value
self.country = country
self.color = color
}
}
Playground testing:
var coinA = Coin(country: "USA", color: "silver")
coinA.merge(Coin(value: 50))
print(coinA.country ?? "nil") // "USA"
print(coinA.color ?? "nil") // "silver"
print(coinA.value ?? "nil") // 50.0
This is not a high-level approach like the merge one you shared the link to but as long as you have a struct to implement the merge feature into, it will do the job.
func merge(other: Coin, keepTracksOfCurrentOnConflict: Bool) -> Coin {
var decidedValue = value
if decidedValue == nil && other.value != nil {
decidedValue = other.value
} else if other.value != nil {
//in this case, it's conflict.
if keepTracksOfCurrentOnConflict {
decidedValue = value
} else {
decidedValue = other.value
}
}
var resultCoin = Coin(value: decidedValue, country: nil, color: nil)
return resultCoin
}
}
You can do the same for other properties.
If you want to wrap it around protocol. The idea behind is the same:
you convert object's to dict
merge two dict's
convert merged dict back to your object
import Foundation
protocol Merge: Codable {
}
extension Dictionary where Key == String, Value == Any {
func mergeAndReplaceWith(object: [Key: Value]) -> [Key: Value] {
var origin = self
origin.merge(object) { (_, new) in
new
}
return origin
}
}
extension Merge {
func toJson() -> [String: Any] {
let jsonData = try! JSONEncoder().encode(self)
let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
return json
}
func merge(object: Merge) -> Merge {
let origin = self.toJson()
let objJson = object.toJson()
let decoder = JSONDecoder()
let merge = origin.mergeAndReplaceWith(object: objJson)
var jsonData = try! JSONSerialization.data(withJSONObject: merge, options: .prettyPrinted)
var mergedObject = try! decoder.decode(Self.self, from: jsonData)
return mergedObject
}
}
struct List: Merge {
let a: String
}
struct Detail: Merge {
struct C: Codable {
let c: String
}
let a: String
let c: C?
}
let list = List(a: "a_list")
let detail_without_c = Detail(a: "a_detail_without_c", c: nil)
let detail = Detail(a: "a_detail", c: Detail.C(c: "val_c_0"))
print(detail.merge(object: list))
print(detail_without_c.merge(object: detail))
Detail(a: "a_list", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
Detail(a: "a_detail", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
With this solution you can actually merge two representations of your endpoint, in my case it is List and Detail.

Array of structs: UserDefaults, how to use?

I've already check all of those topics:
How to save an array of custom struct to NSUserDefault with swift?
How to save struct to NSUserDefaults in Swift 2.0
STRUCT Array To UserDefaults
I have a struct containing some Strings and an other struct: MySection.
struct MySection {
var name: String = ""
var values: [MyRow] = []
}
And there is MyRow which is store in MySection.values
struct MyRow {
var value: String = ""
var quantity: String = ""
var quantityType: String = ""
var done: String = ""
}
Two arrays for use it
var arraySection: [MySection] = []
var arrayRow: [MyRow] = []
And in my application, I add dynamically some values in those arrays.
There is the delegate method for get datas from my second ViewController
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow())
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
And there is the manageSection function.
func manageSection(item: String) {
var i = 0
for _ in arraySection {
if arraySection[i].name == item {
arraySection.insert(MySection(), at: i + 1)
arraySection[i + 1].values = [arrayRow[arrayRow.count - 1]]
return
}
i += 1
}
arraySection.append(MySection())
arraySection[arraySection.count - 1].name = item
arraySection[arraySection.count - 1].values = [arrayRow[arrayRow.count - 1]]
}
My need is to store datas of the two arrays in UserDefaults (or CoreData maybe??) and use these datas when the user going back to the application.
I don't know how to do it, I've already try methods from the 3 topics but I'm not even doing a good job.
How can I do it?
Thanks guys!
Since both types contain only property list compliant types a suitable solution is to add code to convert each type to a property list compliant object and vice versa.
struct MySection {
var name: String
var values = [MyRow]()
init(name : String, values : [MyRow] = []) {
self.name = name
self.values = values
}
init(propertyList: [String: Any]) {
self.name = propertyList["name"] as! String
self.values = (propertyList["values"] as! [[String:String]]).map{ MyRow(propertyList: $0) }
}
var propertyListRepresentation : [String: Any] {
return ["name" : name, "values" : values.map { $0.propertyListRepresentation }]
}
}
struct MyRow {
var value: String
var quantity: String
var quantityType: String
var done: String
init(value : String, quantity: String, quantityType: String, done: String) {
self.value = value
self.quantity = quantity
self.quantityType = quantityType
self.done = done
}
init(propertyList: [String:String]) {
self.value = propertyList["value"]!
self.quantity = propertyList["quantity"]!
self.quantityType = propertyList["quantityType"]!
self.done = propertyList["done"]!
}
var propertyListRepresentation : [String: Any] {
return ["value" : value, "quantity" : quantity, "quantityType" : quantityType, "done" : done ]
}
}
After creating a few objects
let row1 = MyRow(value: "Foo", quantity: "10", quantityType: "Foo", done: "Yes")
let row2 = MyRow(value: "Bar", quantity: "10", quantityType: "Bar", done: "No")
let section = MySection(name: "Baz", values: [row1, row2])
call propertyListRepresentation to get a dictionary ([String:Any]) which can be saved to User Defaults.
let propertyList = section.propertyListRepresentation
Recreation of the section is quite easy, too
let newSection = MySection(propertyList: propertyList)
Edit
Use the propertyList initializer only if you get data from UserDefaults in all other cases use the other initializer.
For example replace
#IBAction func addButtonPressed(_ sender: Any) {
newProducts.append(MyRow(propertyList: ["":""]))
newProducts[newProducts.count - 1].value = nameTextField.text!
newProducts[newProducts.count - 1].quantity = quantityTextField.text!
newProducts[newProducts.count - 1].quantityType = type
newProducts[newProducts.count - 1].done = "No"
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
with
#IBAction func addButtonPressed(_ sender: Any) {
let row = MyRow(value: nameTextField.text!,
quantity: quantityTextField.text!,
quantityType: type,
done: "No")
newProducts.append(row)
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
and replace
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow(propertyList: ["":""]))
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
with
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(newItem[0])
manageSection(item: sectionPick)
listTableView.reloadData()
}
Basically first create the object, then append it to the array. The other way round is very cumbersome.

Swift 3 which is the best way to store of an Object with array etc

I started to develop with Swift 3 and i´m getting crazy. Following Situation:
class subObject
{
var name : String
var list : [Int]
init( _name : String, _list : [Int] ){
self.name = _name
self.list = _list
}
}
class mainObject
{
var subObjectList : [subObject]
init( _list : [subObject] ){
self.subObjectList = _list
}
}
var data : [mainObject]
Which way is state of the art to store var data : [mainObject] persistently. I've already unsuccessfully tried .plistand NSKeyedArchiver.
Sorry but my english is worse.
NSCoding cannot be used because the classes aren't subclasses of NSObject.
Since all properties in both classes are property list compliant you could add a computed property propertyListRepresentation and an appropriate initializer.
Class names are supposed to start with a capital letter and parameters starting with an underscore are unusual in Swift.
class SubObject
{
var name : String
var list : [Int]
init(name : String, list : [Int] ){
self.name = name
self.list = list
}
init?(dictionary : [String:Any]) {
guard let name = dictionary["name"] as? String,
let list = dictionary["list"] as? [Int] else { return nil }
self.name = name
self.list = list
}
var propertyListRepresentation : [String:Any] {
return ["name" : name, "list" : list]
}
}
class MainObject
{
var subObjectList : [SubObject]
init(list : [SubObject] ){
self.subObjectList = list
}
init(propertyList : [[String:Any]] ){
self.subObjectList = propertyList.flatMap{ SubObject(dictionary: $0) }
}
var propertyListRepresentation : [[String:Any]] {
return subObjectList.map{ $0.propertyListRepresentation }
}
}
To use it:
let subs = [SubObject(name: "Foo", list: [1, 2, 3]), SubObject(name: "Bar", list: [4, 5, 6])]
let main = MainObject(list: subs)
let list = main.propertyListRepresentation
let data = try! PropertyListSerialization.data(fromPropertyList: list, format: .xml, options: 0)
print(String(data:data, encoding: .utf8)!)
let restoredList = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:Any]]
let restoredMain = MainObject(propertyList: restoredList)