I'm using both ObjectMapper (https://github.com/Hearst-DD/ObjectMapper) and Realm in my project.
My Objects are all RLMObjects;
I have for example a Blog Object which contains attachments:
dynamic var attachments = RLMArray(objectClassName: Attachment.className())
I have a custom transformer which starts like this:
func transformFromJSON(value: AnyObject?) -> RLMArray? {
let attachments = RLMArray(objectClassName: Attachment.className())
if let str = value as? String {
I can't seem to understand how I could convert them to a RLMArray, I always get nil in my transformer.
"attachments" : [
{
"id" : 2,
"file_name" : "img1.jpg",
"url" : "uploads\/img.jpg"
},
{
"id" : 3,
"file_name" : "img1.jpg",
"url" : "uploads\/img.jpg"
},
{
"id" : 4,
"file_name" : "img1.jpg",
"url" : "uploads\/img.jpg"
}
],
I find it hard to wrap my head around the code, also I dont' find much help in the debugger of xcode.
TL;DR My transformer recieves a nil, or I'm expecting the wrong type how to convert the value to a RLMArray.
Update:
I hope it's getting a bit clear what I'm struggling with.
Anyhow I changed my transformer to contain this for debugging sake:
func transformFromJSON(value: AnyObject?) -> RLMArray? {
let attachments = RLMArray(objectClassName: Attachment.className())
if let val:AnyObject = value {
Debug.log("the val is this \(val)")
Debug.log("the val is this \(val as? String)")
Debug.log("the val is this \(val as? Attachment)")
Debug.log("the val is this \(val as? Array<Attachment>)")
Debug.log("the val is this \(val as? Array<String>)")
Debug.log("the val is this \(val as? Dictionary<String, AnyObject>)")
Debug.log("the val is this \(val as? Dictionary<String, String>)")
let mir = reflect(value)
Debug.log("the mirror is this \(mir)")
}
return attachments
}
Even more confusing, the first Debug.log (which just makes a NSLog). outputs the following:
the val is this (
{
"file_name" = "img.jpg";
id = 2;
url = "uploads/img.jpg";
},
{
"file_name" = "img2.jpg";
id = 3;
url = "uploads/img2.jpg";
},
{
"file_name" = "img3.jpg";
id = 4;
url = "uploads/img3.jpg";
}
)
Which is nor json, nor an array nor a dictionary as far as I can see cause those log lines return:
the val is this nil or, for an array the val is this Optional([])
update
It did seem to be a tuple so now I have this:
func transformFromJSON(value: AnyObject?) -> RLMArray? {
let attachments = RLMArray(objectClassName: Attachment.className())
if let val:AnyObject = value {
Debug.log("-- new obj --")
if let arr = value as? Array<(AnyObject)> {
for file_name in arr {
Debug.log("fn: \(file_name)")
}
}
Which allows me to iterate over the different attachments, now I still have to find out what the inner object is though.
update
After getting a tuple array with "AnyObject" it seems the value was a __NSCFDictionary, which I could fetch values from using objectForKey
So this seems to be the solution how to convert an array of children to my RLMObject Attachment. I'd love some feedback on how wrong I am implementing this and what would be a more proper solution since I feel kinda bad for this:
///Transformer transforming the api's attachment array into a RLMArray with attachments
///NOTE: Back to json is not implemented at this time
class ApiAttachmentsTransform: TransformType {
typealias Object = RLMArray
typealias JSON = String
func transformFromJSON(value: AnyObject?) -> RLMArray? {
let attachments = RLMArray(objectClassName: Attachment.className())
if let val:AnyObject = value {
if let arr = value as? Array<(AnyObject)> {
for obj in arr {
let attachment = Attachment()
if let url = obj.valueForKey("url") as? String {
if let id = obj.valueForKey("id") as? Int {
attachment.url = url
attachment.id = id
attachments.addObject(attachment)
}
}
}
}
}
return attachments
}
func transformToJSON(value: RLMArray?) -> String? {
return nil
}
}
Related
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
}
}
I have a dictionary
var dictionary: Any = ["det" : ["val" : "some","result" : ["key1" : "val1","key2" : "val2"],"key3" :["val1", "val2"]]]
and a mapping function below
func getMappedKey(_ key: String) -> String? {
var mapping: Dictionary = [
"key1" : "key3",
"key2" : "key4",
"det" : "data"
]
return mapping[key]
}
Now I want to change some keys in the same dictionary using the mapping function above. So after the change, the dictionary should look like
["data" : ["val" : "some","result" : ["key3" : "val1","key4" : "val2"],"key3" :["val1", "val2"]]]
So for that I wrote a function below
func translatePayload(_ payload: inout Any) {
if let _ = payload as? String {
return
} else if var payload = payload as? Dictionary<String, Any> {
for (key, value) in payload {
if let newKey = getMappedKey(key) {
if let _ = payload.removeValue(forKey: key) {
payload[newKey] = value
}
}
var nextPayload = value
translatePayload(&nextPayload)
}
} else if let payload = payload as? Array<Any> {
for value in payload {
var nextPayload = value
translatePayload(&nextPayload)
}
}
}
and when I call the function
translatePayload(&dictionary)
print(dictionary)
it does not change the keys in the same dictionary. Can someone please point out what is wrong with this code. Thanks in advance
Your code is perfectly fine, you just updated the local variable instead of the parametric one because you used the same name. Just change the local variable payload to dictionary and array or anything else you like.
Here is the final code:
func translatePayload(_ payload: inout Any) {
if let _ = payload as? String {
return
} else if var dictionary = payload as? Dictionary<String, Any> { // Here dictionary instead of payload
for (key, value) in dictionary {
var nextPayload = value
translatePayload(&nextPayload)
if let newKey = getMappedKey(key) {
if let _ = dictionary.removeValue(forKey: key) {
dictionary[newKey] = nextPayload
}
} else {
dictionary[key] = nextPayload
}
}
payload = dictionary
} else if let array = payload as? Array<Any> { // Here array instead of payload
var updatedArray = array
for (index, value) in array.enumerated() {
var nextPayload = value
translatePayload(&nextPayload)
updatedArray[index] = nextPayload
}
payload = updatedArray // Assign the new changes
}
}
translatePayload(&dictionary)
print(dictionary)
Not really a direct answer to the question "what's wrong", but I'd go with something like:
let dictionary = ["det" : ["val" : "some","result" : ["key1" : "val1", "key2" : "val2"],"key3" :["val1", "val2"]]]
func getMapped(key: String) -> String {
var mapping: Dictionary = [
"key1" : "key3",
"key2" : "key4",
"det" : "data"
]
return mapping[key] ?? key
}
func translate(payload:Any, map:(String)->String) -> Any {
switch payload {
case let value as String:
return value
case let value as [String:Any]:
return value.reduce(into:[String:Any]()) {
$0[map($1.0)] = translate(payload: $1.1, map:map)
}
case let value as [Any]:
return value.map { translate(payload: $0, map:map) }
default:
fatalError("Unknown data type")
}
}
let output = translate(payload: dictionary, map:getMapped(key:))
To really take advantage of the functional spirit of Swift.
I have JSON array like this
var json = NSArray() // array with json objects
//print json >>
json = (
{
Name = "Alen";
Score = 500;
},
{
Name = "John";
Score = 0;
},
{
Name = "Mark";
Score = 2000;
},
{
Name = "Steve";
Score = 300;
},
{
Name = "Ricky";
Score = 900;
}
)
and i can access its objects as
(json[0] as! NSDictionary).object(forKey: "Name")
(json[0] as! NSDictionary).object(forKey: "Score")
I want to sort this JSON array according to scores.
I found the answers like
let sortedArray = json.sorted(by: { $0.0 < $1.0 })
which gives error
Value of type 'Any' has no member '0'
Then I tried this
let sortedArray = (json as! NSDictionary).sorted {(aDic, bDic) -> Bool in
return aDic.key < bDic.key
}
It gave error
Binary operator '<' cannot be applied to two 'Any' operands
Can you please guide me to sort the array according to score in swift 4?
That's a very good example why you are strongly discouraged from using NSArray and NSDictionary in Swift.
Both collection types don't provide type information so everything is treated as Any. Most of the shared generic API of the Swift Standard library cannot be used with Any so you are not able to take advantage of the powerful generic functions unless you add a lot of ugly type casts.
If all values are String declare your array as
var json = [[String:String]]()
Then you can sort the array with
let sortedArray = json.sorted { $0["Score"]! < $1["Score"]! }
The most recommended solution is to decode the JSON directly into a custom struct
struct Player : Decodable {
let name : String
let score : String
private enum CodingKeys : String, CodingKey { case name = "Name", score = "Score" }
}
Then you get rid of all type casting and you can sort by the property name
var players = [Player]()
let jsonString = """
[{"Name" : "Alen", "Score" : "500"},
{"Name" : "John", "Score" : "0"},
{"Name" : "Mark", "Score" : "2000"},
{"Name" : "Steve", "Score" : "300"},
{"Name" : "Ricky", "Score" : "900"}]
"""
let data = Data(jsonString.utf8)
do {
players = try JSONDecoder().decode([Player].self, from: data)
let sortedPlayers = players.sorted{ $0.score.compare($1.score, options: .numeric) == .orderedAscending }
print(sortedPlayers)
} catch { print(error) }
Edit:
To load the JSON use an asynchronous way (URLSession)
Never load data from a remote URL with synchronous Data(contentsOf.
var players = [Player]()
let jsonUrl = URL(string: "url.json")!
let task = URLSession.shared.dataTask(with : url) { [unowned self] (data, _, error) in
if let error = error { print(error); return }
do {
players = try JSONDecoder().decode([Player].self, from: data!).sorted{ $0.score < $1.score }
DispatchQueue.main.async { // reload the table view if necessary
self.tableView.reloadData()
}
} catch { print(error) }
}
task.resume()
After parsing your json, you can sort your score array like this
var scoreArray = ["500", "0", "2000", "300", "900"]
array.sort { $0.compare($1, options: .numeric) == .orderedAscending }
I did something like this before
First I created two arrays of dictionary
var jsonArray = [(name:String, score:String)]()
var sortedscoreArray:[(name: String, score: String)] = []
and in getting json data you can create for loop
for I in 0..< jsonData.count{
Let jsonInfo = jsonData[i]
jsonArray.append((name: jsonInfo[“Name”].string!, score: jsonInfo[“Score"].string!))
}
and after you fill the json array pass it to sortedArray
sortedscoreArray = jsonArray.sorted(by: { $0.score < $1.score })
If array contains dictionary then you can use this code for sorting:
let sortedArray = json.sort { $0["Score"] as! Int < $1["Score"] as! Int }
print(sortedArray)
and if you are using bean class then you can use dot(.) properties for sorting:
let sortedArray = json.sort { $0.Score < $1.Score }
print(sortedArray)
let sortedResults = self.json?.sorted(by: {$0.name ?? EMPTY_STRING < $1.name ?? EMPTY_STRING }) ?? []
I'm using Gloss for my JSON instantiation. Here is a sample class:
public class MyObj: Decodable
{
let id_user : String
let contact_addr1 : String
let contact_addr2 : String?
let contact_city : String
let contact_state : String
let contact_zip : String
let points : Int
// Deserialization
required public init?(json: JSON)
{
guard let id_user : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_addr1 : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_city : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_state : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_zip : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let points : Int = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
self.id_user = id_user
self.contact_addr1 = contact_addr1
self.contact_addr2 = "somekey" <~~ json
self.contact_city = contact_city
self.contact_state = contact_state
self.contact_zip = contact_zip
self.contact_points = points
}
}
I have a lot of model classes. Hundreds of members between them. Writing a multi-line guard statement for each one really junks up my code. Is there any way I can encapsulate the guard functionality into something more concise? Maybe a function or something like:
shortGuard("memberName", "jsonKey")
Maybe there is a way to guard against an array of string keys?
There are a huge variety of ways to accomplish this. They all boil down to writing a wrapper function to map your keys to values. Here are a couple quick examples I thought of, but as I say there are many ways to do this depending on what you're after:
enum JSONError: Error {
case keyNotFound(String)
}
extension JSON {
func values<T>(for keys: [String]) throws -> [T] {
var values = [T]()
for key in keys {
guard let value: T = key <~~ self else {
throw JSONError.keyNotFound(key)
}
values.append(value)
}
return values
}
func values<T>(for keys: [String], closure: ((_ key: String, _ value: T) -> Void)) throws {
for key in keys {
guard let value: T = key <~~ self else {
throw JSONError.keyNotFound(key)
}
closure(key, value)
}
}
}
The first validates all keys before you can use any of them and will throw if one isn't present. You'd use it like so:
do {
let keys = ["foo", "bar"]
// The type of the values constant is important.
// In this example we're saying look for values of type Int.
let values: [Int] = try json.values(for: keys)
for (index, key) in keys.enumerated() {
print("value for \(key): \(values[index])")
}
} catch JSONError.keyNotFound(let key) {
assertionFailure("key not found \(key)")
}
The second one will pass back key, value pairs to a closure as they appear in your keys array and will throw at the first one it finds that doesn't exist.
do {
let keys = ["foo", "bar"]
// The type of the closure's value argument is important.
// In this example we're saying look for values of type String.
try json.values(for: keys) { (key, value: String) in
print("value for key \(key) is \(value)")
}
} catch JSONError.keyNotFound(let key) {
assertionFailure("key not found \(key)")
}
Using the first version in an init?() function for your class, we have something like this:
public struct MyObj: Decodable {
public let id_user : String
public let contact_addr1 : String
public let contact_addr2 : String?
public let points : Int
public init?(json: S) {
do {
let stringKeys = ["id_user", "contact_addr1"]
let stringValues: [String] = try json.values(for: stringKeys)
id_user = stringValues[0]
contact_addr1 = stringValues[1]
// this isn't required, so just extract with no error if it fails
contact_addr2 = "contact_addr2" <~~ json
let intKeys = ["points"]
let intValues: [Int] = try json.values(for: intKeys)
points = intValues[0]
} catch JSONError.keyNotFound(let key) {
assertionFailure("key \(key) not found in JSON")
return nil
} catch {
return nil
}
}
}
I have not used Gloss, and it mostly seems to be unnecessary considering that it is simple enough to parse JSON safely without needing an extra library, or using unfamiliar syntax.
Option 1:
You can group the optional unwrapping in a single guard statement.
Example:
public struct MyObj {
let id_user : String
let contact_addr1 : String
let contact_addr2 : String?
let points : Int
public init?(json: Any) {
guard
let entities = json as? [String : Any],
let id_user = entities["some key"] as? String,
let contact_addr1 = entities["some key"] as? String,
let points = entities["some key"] as? Int
else {
assertionFailure("...")
return nil
}
self.id_user = id_user
self.contact_addr1 = contact_addr1
self.contact_addr2 = entities["some key"] as? String
self.contact_points = points
}
}
Option 2:
Another approach would be to eliminate the guard statements altogether, and let the parser throw an error during parsing, and use an optional try to convert the result to nil.
Example:
// Helper object for parsing values from a dictionary.
// A similar pattern could be used for arrays. i.e. array.stringAt(10)
struct JSONDictionary {
let values: [String : Any]
init(_ json: Any) throws {
guard let values = json as? [String : Any] else {
throw MyError.expectedDictionary
}
self.values = values
}
func string(_ key: String) throws -> String {
guard let value = values[key] as? String else {
throw MyError.expectedString(key)
}
return value
}
func integer(_ key: String) throws -> Int {
guard let value = values[key] as? Int else {
throw MyError.expectedInteger(key)
}
return value
}
}
Parser:
public struct MyObj {
let id_user : String
let contact_addr1 : String
let contact_addr2 : String?
let points : Int
public init(json: Any) throws {
// Instantiate the helper object.
// Ideally the JSONDictionary would be passed by the caller.
let dictionary = try JSONDictionary(json),
self.id_user = try dictionary.string("some key"),
self.contact_addr1 = try dictionary.string("some key"),
self.points = try dictionary.integer("some key")
// Results in an optional if the string call throws an exception
self.contact_addr2 = try? dictionary.string("some key")
}
}
Usage:
// Instantiate MyObj from myJSON.
// myObject will be nil if parsing fails.
let myObject = try? MyObj(json: myJSON)
im using parse in my project, put i faceing issue
here is my code :
func getIdByItemName (name:String) -> String
{
let Query = PFQuery(className: "Class")
.whereKey("name", equalTo: name)
var QueryObject = Query.findObjects()
var findId:String = "????"
for quer in QueryObject!
{
findId = String(quer.objectId)
}
print("The object id id : \(findId)")
return findId
}
my issue is the returned values will be like this :
Optional("l7wpt4slip")
my question is how can i remove optional and keep only object id as String like this ?? :
l7wpt4slip
Here is the safe way to unwrap an optional values:
for quer in QueryObject!
{
//unwrap it with if let.
if let temp = String?(quer.objectId){
findId = temp
}
}