13

Does anyone know how you can use Realm to store an array of strings? I'm trying to map the following response into Realm correctly:

"zoneInfo": {
    "tariffInfoLines": [
        "In this city you pay per minute."
    ]
}

We have a zoneInfo object that contains a tariffInfoLines array. This tariffInfoLines array contains strings. In Realm there are two different variable types for storing data. The first is RLMObject which allows your standard NSString, int, long etc.

The second type is RLMArray, which is used for arrays (as NSArray is not supported). You have to give the array a type, which must be a class that subclasses RLMObject. We have so far got around this by using a ABCRealmString object, as shown below:

@property RLMArray<ABCRealmString> *tariffInfoLines;

ABCRealmString contains an NSString property (it is basically a wrapper):

@property NSString *value;

However what this means is that when Realm tries to map the response to persist the data, it is looking for a value for the key "value" (the name of the property). It appears that it expects a response similar to the following:

"zoneInfo": {
    "tariffInfoLines": [
        {
            "value": "In this city you pay per minute."
        },
    ]
}

In the project, we have it working for the following structure:

"userOptions": [
    {
        "wantsEmailNotifications": true,
        "wantsPushNotifications": false
    },
]

This has an array, with objects inside that have clear key value pairs that Realm can map to. The zoneInfo structure appears to be the only place that we have an array with sets of values inside without them being inside an object or having any keys.

If anyone could shed some light on this, regarding if this is possible using Realm, or whether an API change is required to match a structure that Realm can map.

dandan78
  • 13,328
  • 13
  • 64
  • 78
Jamie
  • 927
  • 1
  • 7
  • 18

5 Answers5

34

Cross posting from the github issue response: Although this example demonstrates how to store flat arrays of strings on a Realm model, you can extend this pattern to store anything from arrays of integers to native Swift enum's. Basically anything that you can map to a representable type in Realm.

class RealmString: Object {
    dynamic var stringValue = ""
}

class Person: Object {
    var nicknames: [String] {
        get {
            return _backingNickNames.map { $0.stringValue }
        }
        set {
            _backingNickNames.removeAll()
            _backingNickNames.appendContentsOf(newValue.map({ RealmString(value: [$0]) }))
        }
    }
    let _backingNickNames = List<RealmString>()

    override static func ignoredProperties() -> [String] {
        return ["nicknames"]
    }
}

// Usage...

let realm = try! Realm()
try! realm.write {
    let person = Person()
    person.nicknames = ["John", "Johnny"]
    realm.add(person)
}

for person in realm.objects(Person) {
    print("Person's nicknames: \(person.nicknames)")
}

// Prints:
// Person's nicknames: ["John", "Johnny"]
jpsim
  • 14,329
  • 6
  • 51
  • 68
yoshyosh
  • 13,956
  • 14
  • 38
  • 46
  • 6
    I got an error ```Cannot assign a value of type [RealmString] to a value of type 'List'```. How can I convert Array of RealmString to a List? – Nicopuri Aug 04 '15 at 16:04
  • I'm having the exact same issue myself at the moment. – damianesteban Sep 23 '15 at 08:46
  • 2
    @jpsim can you confirm or infirm the claim from the other answer that: "_The RealmString approach is good, but you end up with a new RealmString every time you update the values, leaving a ton of unused objects laying around if you don't clean them up._". If correct, **how to clean the unused objects?** – Cœur Jan 06 '17 at 09:59
  • The nicest approach would be if Realm had support for cascading deletes, which is in the works, but not slated for release anytime soon. Instead, you could iterate over these `RealmString` objects at app launch (or another convenient time) and delete the ones with no "linking objects" (e.g. unused ones). See https://realm.io/docs/swift/latest/#inverse-relationships – jpsim Jan 06 '17 at 18:13
  • 1
    Yea, you will end up with the new string everytime you update (since you dont have a primary key here). Cascading delete is not implemented yet, but you can add something like a `parentID` or create a primary key yourself. – Jakub Truhlář Feb 23 '17 at 13:12
8

UPDATE (most of the previous answers are no longer correct):

You can now store primitive types or their nullable counterparts (more specifically: booleans, integer and floating-point number types, strings, dates, and data) directly within RLMArrays or Lists. If you want to define a list of such primitive values you no longer need to define cumbersome single-field wrapper objects. Instead, you can just store the primitive values themselves.

Lists of primitive values work much the same way as lists containing objects, as the example below demonstrates for Swift:

class Student : Object {
    @objc dynamic var name: String = ""
    let testScores = List<Int>()
}

// Retrieve a student.
let realm = try! Realm()
let bob = realm.objects(Student.self).filter("name = 'Bob'").first!

// Give him a few test scores, and then print his average score.
try! realm.write {
    bob.testScores.removeAll()
    bob.testScores.append(94)
    bob.testScores.append(89)
    bob.testScores.append(96)
}
print("\(bob.testScores.average()!)")   // 93.0

All other languages supported by Realm also supports lists of primitive types.

bmunk
  • 1,488
  • 1
  • 13
  • 22
3

For the Swift 3.0 here is the change (in my case the Xcode 8 compiler didn't offer auto fix when i switched to swift 3.0 so I had some pain to resolve it).

_backingNickNames.append(objectsIn: newValue.map { RealmString(value: [$0]) })
Pavle Mijatovic
  • 773
  • 10
  • 6
  • With current Xcode, a simple click on the red dot of the error will auto-correct the old syntax to the new one. – Cœur Jan 06 '17 at 09:36
2

The RealmString approach is good, but you end up with a new RealmString every time you update the values, leaving a ton of unused objects laying around if you don't clean them up.

I would suggest using something like:

fileprivate let separator = "\u{FFFF}"

class Person: Object {
    fileprivate dynamic var _nicknames: String?
    var nicknames: [String] {
        get { return _nicknames?.components(separatedBy: separator) ?? [] }
        set { _nicknames = newValue.isEmpty ? nil : newValue.joined(separator: separator) }
    }

    override static func ignoredProperties() -> [String] {
        return ["nicknames"]
    }
}
Rok Gregorič
  • 2,377
  • 1
  • 13
  • 11
  • so you suggest the CSV technique with a different Unicode character to replace the comma? Let me quote [this answer](https://stackoverflow.com/a/16619933/1033581) to demonstrate that **your suggestion should be avoided in a general case**: "_The Unicode Consortium recently issued [Corrigendum 9](http://www.unicode.org/versions/corrigendum9.html) that clarifies the role of noncharacters, including U+FFFF, in Unicode strings. It states that while noncharacters are intended for internal use, they can occur **legally** in Unicode strings._" But for specific cases, it may work. – Cœur Jan 06 '17 at 09:43
0
extension String {
    func toStringObject() -> StringObject {
        return StringObject(initValue: self)
    }
}

extension Sequence where Iterator.Element == String {
    func toStringObjects() -> List<StringObject> {
        let list = List<StringObject>()
        for s in self {
            list.append(s.toStringObject())
        }
        return list
    }
}

extension Int {
    func toIntObject() -> IntObject {
        return IntObject(initValue: self)
    }
}

extension Sequence where Iterator.Element == Int {
    func toIntObjects() -> List<IntObject> {
        let list = List<IntObject>()
        for s in self {
            list.append(s.toIntObject())
        }
        return list
    }
}
Ace
  • 219
  • 1
  • 3
  • You better explain your solution than just posting some anonymous code. You should read [How do I write a good answer](https://stackoverflow.com/help/how-to-answer) – Massimiliano Kraus Aug 10 '17 at 12:13
  • thanks for your opinion ! when your's data have like that array of string or array array of int and i tried my solution and it success . – Ace Nov 30 '17 at 04:26