How can I convert a Realm object to JSON in Swift? - json

I have two Realm tables declared:
class Task: Object {
dynamic var taskID: String = ""
let taskAssignedTo = List<Contacts>()
}
class Contacts: Object {
dynamic var contactEmail: String = ""
dynamic var contactName: String = ""
}
Final goal is to convert the Task Realm object into JSON. The method I'm thinking of is:
Convert the object to a dictionary using a method within the class
func taskToDictionary() -> [String: AnyObject] {
return [
"taskID" : self.taskID,
"taskAssignedTo" : self.taskAssignedTo._rlmArray.count //Not sure how to get the array
]
}
Convert the resulting dictionary into JSON with SwiftyJSON
let taskObject = Task()
let newTaskJSON = JSON(taskObject.taskToDictionary())
Right now, this converts ok, but:
Is there a better way to do this?
How can I convert the RLMArray into an array for JSON conversion?

Managed to find the answer here:
Can I serialize a RealmObject to JSON or to NSDictionary in Realm for Swift?
extension Object {
func toDictionary() -> NSDictionary {
let properties = self.objectSchema.properties.map { $0.name }
let dictionary = self.dictionaryWithValuesForKeys(properties)
var mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeysWithDictionary(dictionary)
for prop in self.objectSchema.properties as [Property]! {
// find lists
if let objectClassName = prop.objectClassName {
if let nestedObject = self[prop.name] as? Object {
mutabledic.setValue(nestedObject.toDictionary(), forKey: prop.name)
} else if let nestedListObject = self[prop.name] as? ListBase {
var objects = [AnyObject]()
for index in 0..<nestedListObject._rlmArray.count {
if let object = nestedListObject._rlmArray[index] as? Object {
objects.append(object.toDictionary())
}
}
mutabledic.setObject(objects, forKey: prop.name)
}
}
}
return mutabledic
}
}
Update for Xcode 7 & Swift 2:
extension Object {
func toDictionary() -> NSDictionary {
let properties = self.objectSchema.properties.map { $0.name }
let dictionary = self.dictionaryWithValuesForKeys(properties)
let mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeysWithDictionary(dictionary)
for prop in self.objectSchema.properties as [Property]! {
// find lists
if let nestedObject = self[prop.name] as? Object {
mutabledic.setValue(nestedObject.toDictionary(), forKey: prop.name)
} else if let nestedListObject = self[prop.name] as? ListBase {
var objects = [AnyObject]()
for index in 0..<nestedListObject._rlmArray.count {
let object = nestedListObject._rlmArray[index] as AnyObject
objects.append(object.toDictionary())
}
mutabledic.setObject(objects, forKey: prop.name)
}
}
return mutabledic
}
}
Update to Xcode 8 and Swift 3 :
extension Object {
func toDictionary() -> NSDictionary {
let properties = self.objectSchema.properties.map { $0.name }
let dictionary = self.dictionaryWithValues(forKeys: properties)
let mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeys(dictionary)
for prop in self.objectSchema.properties as [Property]! {
// find lists
if let nestedObject = self[prop.name] as? Object {
mutabledic.setValue(nestedObject.toDictionary(), forKey: prop.name)
} else if let nestedListObject = self[prop.name] as? ListBase {
var objects = [AnyObject]()
for index in 0..<nestedListObject._rlmArray.count {
let object = nestedListObject._rlmArray[index] as AnyObject
objects.append(object.toDictionary())
}
mutabledic.setObject(objects, forKey: prop.name as NSCopying)
}
}
return mutabledic
}
}

As i can't comment, #Eric
Based on #Eugene Teh answer
I had to do a specific treatment for date. Here is my code (swift 3)
I get the value first
if let value = self.value(forKey: props.name) {
if props.type == .date {
mutabledic[props.name] = (value as! Date).timeIntervalSince1970
//for using like the example, this should work
//mutabledic.setObject( (value as! Date).timeIntervalSince1970, forKey: prop.name as NSCopying)
}
[..]//other case
}

Swift 4.2 Xcode 11
This is how i solved the issue. To covert Realm Objects into JSON Array for sending to the Rest APIs
Requirement - SwiftyJSON
func getJsonArray(){
var dicArray = [Dictionary<String,AnyObject>]()
for item in cartsData! {
dicArray.append(item.toDictionary())
}
print(JSON(dicArray))
}
cartsData - var cartsData : Results<...>?
extension Object {
func toDictionary() -> [String:AnyObject] {
let properties = self.objectSchema.properties.map { $0.name }
var dicProps = [String:AnyObject]()
for (key, value) in self.dictionaryWithValues(forKeys: properties) {
//key = key.uppercased()
if let value = value as? ListBase {
dicProps[key] = value.toArray1() as AnyObject
} else if let value = value as? Object {
dicProps[key] = value.toDictionary() as AnyObject
} else {
dicProps[key] = value as AnyObject
}
}
return dicProps
}
}
extension ListBase {
func toArray1() -> [AnyObject] {
var _toArray = [AnyObject]()
for i in 0..<self._rlmArray.count {
let obj = unsafeBitCast(self._rlmArray[i], to: Object.self)
_toArray.append(obj.toDictionary() as AnyObject)
}
return _toArray
}
}

Related

How to get json fields?

I follow a lesson from one course
And I need to get json, but i want get another json than in a lesson.
So this is my json:
https://api.scryfall.com/cards/search?q=half
And code:
struct Card {
var cardId: String
var name: String
var imageUrl: String
var text: String
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String,
let cardId = dict["cardId"] as? String,
let imageUrl = dict["imageUrl"] as? String,
let text = dict["text"] as? String else { return nil }
self.cardId = cardId
self.name = name
self.imageUrl = imageUrl
self.text = text
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping(GetCardResponse) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
do {
print ("ok1")
let response = try GetCardResponse(json: json)
print ("ok2")
completion(response)
} catch {
print(error)
}
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
print(json)
} catch {
print(error)
}
}.resume()
}
}
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let array = json as? [[String: AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in array {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
Problem in struct GetCardResponse and [[String: AnyObject]] because I dont know how to parse this type of json. I tried to change them in the likeness of json. But I dont really understand how it works and in which part of code i need to put json["data"] or something like this... Help pls. I just want get json fields tcgplayer_id, name, art_crop
As of your code, you can parse the required details as:
struct Card {
var cardId: String = ""
var name: String = ""
var imageUrl: String = ""
var text: String = ""
init(dict: [String: Any]) {
if let obj = dict["name"] {
self.name = "\(obj)"
}
if let obj = dict["tcgplayer_id"] {
self.cardId = "\(obj)"
}
if let obj = dict["image_uris"] as? [String:Any], let url = obj["art_crop"] {
self.imageUrl = "\(url)"
}
if let obj = dict["oracle_text"] {
self.text = "\(obj)"
}
}
static func models(array: [[String:Any]]) -> [Card] {
return array.map { Card(dict: $0) }
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping([Card]?) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
print ("ok1")
if let jData = json as? [String:Any], let data = jData["data"] as? [[String:Any]] {
let response = Card.models(array: data)
completion(response)
}
completion(nil)
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
} catch {
print(error)
}
}.resume()
}
}
CardNetworkService.getCards(url: "https://api.scryfall.com/cards/search?q=half") { (res) in
print(res ?? [])
}
Just paste this code in playground and it'll work.
Happy Coding :)
You are wrong get entry of data field.
First you need get data field in json. And parse to deeper.
Try use the code.
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let jsonObject = json as? [String: Any], let data = jsonObject["data"] as? [[String:AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in data {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
UPDATE:
init function in Card has something wrong. In your json cardId is not found
Card class maybe like this because cardId, imageUrl, text maybe not found. It is optional
struct Card {
var cardId: String?
var name: String
var imageUrl: String?
var text: String?
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String else { return nil }
self.cardId = dict["cardId"] as? String
self.name = name
self.imageUrl = dict["imageUrl"] as? String
self.text = dict["text"] as? String
}
}
Try using Codable to parse the JSON data like so,
Create the models like,
struct Root: Decodable {
let cards: [Card]
enum CodingKeys: String, CodingKey {
case cards = "data"
}
}
struct Card: Decodable {
let tcgplayerId: Int
let name: String
let artCrop: String
}
Now parse your JSON data using,
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
}
You can access the properties in cards of response like so,
response.cards.first?.tcgplayerId

Swift nested JSON Objects not decoding

I'm trying to decode an array of nested objects within Swift and it's not working. The nested objects are of the same type which I'm using Realm to store the values and look like:
struct myObject: Codable {
var name: String?
let otherObjects = List<myObject>()
var events: [String]?
...
if let objects = try container.decodeIfPresent([myObject].self, forKey: .otherObjects) {
otherObjects.append(objectsIn: objects)
}
}
Decoder Function - This works for all other fields inside the object at top level including events which is an array of String.
static func decodeResponse<T:Codable>(_ response : Any?) -> T? {
if let response = response, let dict = response as? [String:Any] {
var dictionaryToParse = dict
var arrayToParse: [[String:Any]]?
if let dataObject = dict["data"] as? [String:Any] {
//sometimes the response has an outer "data" OBJECT
dictionaryToParse = dataObject
} else if let dataArray = dict["data"] as? [[String:Any]] {
//sometimes the response has an outer "data" ARRAY
arrayToParse = dataArray
}
do {
guard let data = try? JSONSerialization.data(withJSONObject: arrayToParse ?? dictionaryToParse, options: []) else { return nil }
let obj = try JSONDecoder().decode(T.self, from: data)
print("DECODE: \(obj)")
return obj
}
catch let error {
print("ERROR: (decodeResponse): \(error)")
return nil
}
}
return nil
}
The coding keys map as they're the same type and nothing gets outputted as an error.

Get a type of variable and declare an other variable with this type in Swift

I currently exploring the JSON world in swift and I've some trouble to make a clean code.
Let's say I've the following structure
struct Foo {
var a: String = ""
var b: Int = 0
}
I use reflection to get a dictionary of the label : value of this struct with this function:
static func dictionaryRepresentation() -> [String : AnyObject] {
var dictionnary = [String : AnyObject]()
for child in Mirror(reflecting: self).children {
dictionnary[child.label!] = child.value as AnyObject
}
return dictionnary
}
Now I have a dictionary of [String : AnyObject] and here comes the issue.
I would like to be able to do something like this
class JSONManager {
class func decode<T>(fromData data: Data) -> [T]? where T : JSONModelProtocol {
let jsonObject = try? JSONSerialization.jsonObject(with: data,
options: [])
guard let jsonDictionary = jsonObject as? [AnyObject] else {
return nil
}
guard let json = jsonDictionary as? [[String : Any]] else {
return nil
}
let representation = T.dictionaryRepresentation()
for jsonItem in json { // json is a dictionary [String : Any]
for (label, value) in object {
let type = Mirror.init(reflecting: value).subjectType // The type of the item
// Same whit let type = type(of: value)
let item = jsonItem[label] as? type // This line doesn't work I cannot cast here
}
}
}
Any idea about how to achieve this?

Swift 3: NSArray element failed to match the Swift Array Element type

I am trying to parse JSON in swift 3 below is my JSON file. And try to get in a array of class which i have declared. But getting error: fatal error: NSArray element failed to match the Swift Array Element type.
{
"Headers": [
{
"headerName": "Home",
"sortByNo" : 1,
"headerImageName": "header0",
"viewCotrollerName": "InitialViewController"
},
{
"headerName": "About",
"sortByNo" : 2,
"headerImageName": "header1",
"viewCotrollerName": ""
},
{
"headerName": "Timing",
"sortByNo" : 3,
"headerImageName": "header3",
"viewCotrollerName": "TimingViewController"
}
]
}
// Class Type
class JsonObjectClass {
var headerName = ""
var sortByNo = ""
var headerImageName = ""
var viewControllerName = ""
}
var array = [JsonObjectClass]() // my array of class type
//JSON Parsing Code
func parseLocalFile() {
let url = Bundle.main.url(forResource: "HeaderFileD", withExtension: "json")
let data = NSData(contentsOf: url!)
do {
let jsonData = try JSONSerialization.jsonObject(with: data! as Data, options: .mutableContainers) as! NSDictionary
array = jsonData.object(forKey: "Headers") as! [JsonObjectClass]
// I am getting error here "fatal error: NSArray element failed to match the Swift Array Element type"
for arr in array {
print(arr)
}
} catch {
}
}
You cannot assign an array or dictionary to a custom class directly.
You need to map the array by creating instances of your class.
I changed the class to a struct to get the member-wise initializer. By the way, the value for key sortByNo is an Int
struct JsonObjectClass {
var headerName = ""
var sortByNo = 0
var headerImageName = ""
var viewControllerName = ""
}
var array = [JsonObjectClass]() // my array of class type
//JSON Parsing Code
func parseLocalFile() {
guard let url = Bundle.main.url(forResource: "HeaderFileD", withExtension: "json") else { return }
do {
let data = try Data(contentsOf: url)
let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as! [String:Any]
let jsonArray = jsonData["Headers"] as! [[String:Any]]
array = jsonArray.map { JsonObjectClass(headerName: $0["headerName"] as! String,
sortByNo: $0["sortByNo"] as! Int,
headerImageName: $0["headerImageName"] as! String,
viewControllerName: $0["viewCotrollerName"] as! String)}
for arr in array {
print(arr)
}
} catch let error as NSError {
print(error)
}
}
PS: Consider the typo viewControllerName vs viewCotrollerName

swift parsing JSON data

so am trying to learn about JSON parsing, i want to extract some information from these fields..
index = 90;
property1 = {
href = "http://www.bodybuilding.com/exercises/detail/view/name/supine-one-arm-overhead-throw";
text = "Supine One-Arm Overhead Throw";
};
property2 = {
href = "http://www.bodybuilding.com/exercises/finder/lookup/filter/muscle/id/13/muscle/abdominals";
text = Abdominals;
};
property3 = (
{
href = "http://www.bodybuilding.com/exercises/detail/view/name/supine-one-arm-overhead-throw";
src = "http://www.bodybuilding.com/exercises/exerciseImages/sequences/839/Male/m/839_1.jpg";
text = "";
},
i can get a chunk of data, the problem is when i try to sort this information out... here is my code
func parseDictionary(dictionary: [String: AnyObject]) {
if let array: AnyObject = dictionary["results"] {
for resultDict in array as![AnyObject] {
if let resultDict = resultDict as? [String:AnyObject] {
if let wrapperType = resultDict["wrapperType"] as? String {
if let kind = resultDict["kind"] as? String {
print("wrapperType: \(wrapperType), kind: \(kind)")
}
}
} else {
print("expected a dictionary")
}
}
} else {
print("expected results array")
}
}
the error am getting is..
//Could not cast value of type '__NSCFDictionary' (0x1014c8a60) to //'NSArray' (0x1014c8470).
Your line:
for resultDict in array as![AnyObject] {
Needs to change to
for resultDict in array as![String: AnyObject] {
[AnyObject] is shorthand for Array<AnyObject>, whereas [String: AnyObject] is shorthand for Dictionary<String, AnyObject>, which explains your error.