Skip to content

Instantly share code, notes, and snippets.

@Jerrot
Last active March 17, 2024 13:10
Show Gist options
  • Save Jerrot/fe233a94c5427a4ec29b to your computer and use it in GitHub Desktop.
Save Jerrot/fe233a94c5427a4ec29b to your computer and use it in GitHub Desktop.
Transform arrays with ObjectMapper to Realm's List type
// Based on Swift 1.2, ObjectMapper 0.15, RealmSwift 0.94.1
// Author: Timo Wälisch <[email protected]>
import UIKit
import RealmSwift
import ObjectMapper
import SwiftyJSON
class ArrayTransform<T:RealmSwift.Object where T:Mappable> : TransformType {
typealias Object = List<T>
typealias JSON = Array<AnyObject>
let mapper = Mapper<T>()
func transformFromJSON(value: AnyObject?) -> List<T>? {
var result = List<T>()
if let tempArr = value as! Array<AnyObject>? {
for entry in tempArr {
let mapper = Mapper<T>()
let model : T = mapper.map(entry)!
result.append(model)
}
}
return result
}
// transformToJson was replaced with a solution by @zendobk from https://gist.github.com/zendobk/80b16eb74524a1674871
// to avoid confusing future visitors of this gist. Thanks to @marksbren for pointing this out (see comments of this gist)
func transformToJSON(value: Object?) -> JSON? {
var results = [AnyObject]()
if let value = value {
for obj in value {
let json = mapper.toJSON(obj)
results.append(json)
}
}
return results
}
}
// SampleModel.swift
// Author: Timo Wälisch <[email protected]>
import UIKit
import ObjectMapper
import RealmSwift
import SwiftyJSON
class SampleModel: Object, Mappable {
// MARK: Realm - stored properties
dynamic var title: String = ""
var products = List<ProductModel>()
// MARK: ObjectMapper
class func newInstance(map: Map) -> Mappable? {
return SampleModel()
}
/// Mapping between ObjectMapper (JSON) and the model properties
func mapping(map: Map) {
title <- map["title"]
products <- (map["products"], ArrayTransform<ProductModel>())
}
}
@tristanhimmelman
Copy link

It would be great is the ArrayTransform could be added to ObjectMapper. Please send over a PR

@Taco55
Copy link

Taco55 commented Sep 4, 2016

I have an issue with ArrayTransform to serialize nested objects for use with Alamofire. The serialization end up with a SwiftDeferred array (see output in the example code). What would be the proper way to use ArrayTransform?

class Example: Object, Mappable {
    dynamic var foo: String = "bar"
    var qux = List<Qux>()

    required convenience init?(_ map: Map) {
        self.init()
    }

    func mapping(map: Map) {
        self.foo <- map["foo"]
        self.qux <-  (map["qux"], ArrayTransform<Qux>())
    }
}

class Qux: Object, Mappable {
    dynamic var x = 1

    required convenience init?(_ map: Map) {
        self.init()
    }

    func mapping(map: Map) {
        self.x <- map["x"]
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let realm = try! Realm()
        if realm.isEmpty {
            try! realm.write {
                let example = Example()
                example.qux.appendContentsOf([1,2,3].map { Qux(value: [$0]) })
                realm.add(example)
            }
        }

        let example = realm.objects(Example.self).first!

        try! realm.write {
            print( Mapper().toJSON(example) )

                /*
                // Gives a SwiftDeferredNSArray
                ["qux": <_TtCs21_SwiftDeferredNSArray 0x7fa9904cdc30>(
                    {
                        x = 1;
                    },
                    {
                        x = 2;
                    },
                    {
                        x = 3;
                    }
                    )
                    , "foo": bar]
                 */

            print( Mapper().toJSONString(example, prettyPrint: true)! )
            /* 
            // Gives output as expected
                {
                    "qux" : [
                    {
                        "x" : 1
                    },
                    {
                        "x" : 2
                    },
                    {
                        "x" : 3
                    }
                    ],
                    "foo" : "bar"
                }
             */

        }


        // Expected result would be:
        let fieldsExampleObject = [ "foo": "bar", "qux": [ ["x": 1], ["x": 2], ["x": 3] ] ]
        print(fieldsExampleObject)
        /*
             ["qux": <__NSArrayI 0x7fdd936c00c0>(
                 {
                    x = 1;
                 },
                 {
                    x = 2;
                 },
                 {
                    x = 3;
                 }
             ),
             "foo": bar]
        */

    }

}

@fahlout
Copy link

fahlout commented Nov 2, 2016

Any way to get a version that works with swift 3 and swift 3 Realm?

@s-petersson
Copy link

Here is a version that should work with Swift 3 :) @fahlout

import UIKit
import RealmSwift
import ObjectMapper

class ArrayTransform<T:RealmSwift.Object> : TransformType where T:Mappable {
    typealias Object = List<T>
    typealias JSON = Array<AnyObject>
    
    func transformFromJSON(_ value: Any?) -> List<T>? {
        let realmList = List<T>()

        if let jsonArray = value as? Array<Any> {
            for item in jsonArray {
                if let realmModel = Mapper<T>().map(JSONObject: item) {
                    realmList.append(realmModel)
                }
            }
        }

        return realmList
    }
    
    func transformToJSON(_ value: List<T>?) -> Array<AnyObject>? {

        guard let realmList = value, realmList.count > 0 else { return nil }

        var resultArray = Array<T>()

        for entry in realmList {
            resultArray.append(entry)
        }

        return resultArray
    }
}

@RajaveluC
Copy link

RajaveluC commented Dec 21, 2016

@Pendla
I am using the above swift 3.0 version of ArrayTransform and I still couldn't get around parsing nested objects succesfully.

Here are my classes.

class User : Object, Mappable {

    dynamic var userId: String!
    dynamic var emailId: String!
    
    override class func primaryKey() -> String? {
        return "userId"
    }
    
    required convenience init?(map: Map) {
        self.init()
    }
    
    func mapping(map: Map) {
        userId <- map["userId"]
        emailId <- map["email"]
    }
}

class ContactGroup : Object, Mappable {
    dynamic var groupId: String!
    var users = List<User> ()
    
    override class func primaryKey() -> String? {
        return "groupId"
    }
    
    required convenience init?(map: Map) {
        self.init()
    }
    
    func mapping(map: Map) {
        groupId <- map["contactGroupId"]
        users <- (map["users"], ArrayTransform<User>())
    }
}

func storeContactGroups (groups : NSArray)  {
        var groupRealms = Array<ContactGroup> ()
        for object in groups {
            let group : ContactGroup! = ContactGroup(JSON: object as! [String : Any])
            groupRealms.append(group)
        }
        
        let realm = try! Realm()
        try! realm.write {
            for group in groupRealms {
                realm.add(group, update: true)
            }
        }
 }

After the storeContactGroups is executed, I still see zero Users in my Realm file under ContactGroup. What am I doing wrong here?

screen shot 2016-12-21 at 2 52 23 pm

@danipralea
Copy link

danipralea commented Dec 23, 2016

I'm having the same error. Mapping succeeds, but somehow saving the object (with the list of objects) invalidates the list and sets that list to nil.
And from what I've seen, you don't even need the extension for ArrayTransform. The latest version of ObjectMapper successfully maps an array of objects.

@borut-t
Copy link

borut-t commented Jan 11, 2017

Here is the simplified version using swift 3+

func transformToJSON(_ value: Object?) -> JSON? {
  var results = [[String:Any]]()
  if let value = value {
    results.append(contentsOf: value.map({ self.mapper.toJSON($0) }))
  }
  return results as ArrayTransform.JSON?
}

@pabloruan0710
Copy link

Very Crazy, if properties is managed in Realm, the correct is used let for declaration of List and not var ?

@gulzatique
Copy link

gulzatique commented Apr 6, 2017

Thanks it worked for me But I needed to change it according to Swift warnings and errors and this is what I ended up with:

class ArrayTransform<T:RealmSwift.Object> : TransformType where T:Mappable {
    typealias Object = List<T>
    typealias JSON = Array<AnyObject>
    
    let mapper = Mapper<T>()
    
    func transformFromJSON(_ value: Any?) -> List<T>? {
        let result = List<T>()
        if let tempArr = value as! Array<AnyObject>? {
            for entry in tempArr {
                let mapper = Mapper<T>()
                let model : T = mapper.map(JSON: entry as! [String : Any])!
                result.append(model)
            }
        }
        return result
    }
    
    func transformToJSON(_ value: Object?) -> JSON? {
        var results = [AnyObject]()
        if let value = value {
            for obj in value {
                let json = mapper.toJSON(obj)
                results.append(json as AnyObject)
            }
        }
        return results
    }
}

@calvinsug
Copy link

What if i want to Map my List of Integer or String object? @pendla
I still can't solve it

class X: Object, StaticMappable {
 var strings = List<RealmString>()

 class func objectForMapping(map: Map) -> BaseMappable? {
     return X()
 }

func mapping(map: Map) {
      strings <- (map["strings"], ListTransform<RealmString>())
}
}

class RealmString: Object, StaticMappable {
 dynamic var value = ""
 
 class func objectForMapping(map: Map) -> BaseMappable? {
     return RealmString()
 }
 
 func mapping(map: Map) {
     value <- map
 }

}

Json should be like this :
{
x: ["a","b","c"]
}

@miralem-cebic
Copy link

miralem-cebic commented Dec 8, 2017

Thanks for sharing this code!

Here is my solution for Swift 4.0.2 & Xcode 9.2

import RealmSwift
import ObjectMapper

class ArrayTransform<T:RealmSwift.Object> : TransformType where T:Mappable {
    typealias Object = List<T>
    typealias JSON = Array<AnyObject>

    func transformFromJSON(_ value: Any?) -> List<T>? {
        let result = List<T>()
        if let tempArr = value as! Array<AnyObject>? {
            for entry in tempArr {
                let mapper = Mapper<T>()
                let model : T = mapper.map(JSONObject: entry)!
                result.append(model)
            }
        }
        return result
    }

    func transformToJSON(_ value: List<T>?) -> Array<AnyObject>? {
        if (value!.count > 0) {
            var result = Array<T>()
            for entry in value! {
                result.append(entry)
            }
            return result
        }
        return nil
    }
}

@nadia-am
Copy link

nadia-am commented Jan 1, 2018

@sudeep23 did you find any solution? i have the same problem.

@levibostian
Copy link

@calvinsug I have encountered that problem myself. Storing a list of primitive data types in a Realm List apposed to storing a Realm List of Realm Models.

I give a possible solution here: https://stackoverflow.com/a/54581186/1486374

@ElonPark
Copy link

Thanks

I 'm use not optional forced unwrapping
This is my solution for Swift 4.2 & Xcode 10.1

import RealmSwift
import ObjectMapper

class ArrayTransform<T: RealmSwift.Object>: TransformType where T: Mappable {
    typealias Object = List<T>
    typealias JSON = Array<AnyObject>
    
    /**
     - Parameter value: JSON Value
     - Returns: if value is `nil` or not Array will be return empty List<T>
    */
    func transformFromJSON(_ value: Any?) -> Object? {
        let result = Object()
        guard let _value = value,
            let objectArray = _value as? Array<AnyObject> else { return result }

        let mapper = Mapper<T>()
        
        for object in objectArray {
            //if model is `nil` continue to next object
            guard let model = mapper.map(JSONObject: object) else {
                continue
            }
            
            result.append(model)
        }
        
        return result
    }
    
    /**
     - Parameter value: RealmSwift Object
     - Returns: if value is `nil` or empty will be return empty Array<AnyObject>
     */
    func transformToJSON(_ value: Object?) -> JSON? {
        var result = JSON()
        guard let _value = value, _value.count > 0 else { return  result }
        
        result = _value.map { $0 }
      
        
        return result
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment