Realm-cocoa: Storing an array of strings using Realm's RLMArray [Objective-C]

Created on 30 Jul 2015  路  13Comments  路  Source: realm/realm-cocoa

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.

T-Help

Most helpful comment

Here's a revised version:

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 class func ignoredProperties() -> [String] {
        return ["nicknames"]
    }
}

Usage:

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

print(realm.objects(Person.self).first!.nicknames)
// Prints: ["Doug", "Dougie"]

All 13 comments

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 = newValue.map { RealmString(value: [$0]) }
        }
    }
    dynamic var _backingNickNames = List<RealmString>()
}

Thanks for your response @jpsim. I'm looking to persist data using realm with an array that has no keys. Please see an example below:

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

Using the solution outlined above, this results in the following error:

Invalid value 'In this city you pay per minute.' to initialize object of type 'ABCRealmString': missing key 'value'

Call stack
0   __exceptionPreprocess
1   objc_exception_throw
2   RLMValidatedValueForProperty
3   RLMCreateObjectInRealmWithValue

RLMValidatedValueForProperty is available in RLMObjectBase.mm on Line 334

id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) {
    @try {
        return [object valueForKey:key];
    }
    @catch (NSException *e) {
        if ([e.name isEqualToString:NSUndefinedKeyException]) {
            @throw RLMException([NSString stringWithFormat:@"Invalid value '%@' to initialize object of type '%@': missing key '%@'",
                                 object, className, key]);
        }
        @throw;
    }
}

Parameters passed in:

object = "In this city you pay per minute." [NSString]
key = "value" [NSString]
className = "ABCRealmString" [NSString]

ABCRealmString:

@interface PMGRealmString : RLMObject
    @property NSString *value;
@end

If this returned object when there are no keys, then it would allow a flat structure (as previously mentioned) to be used with Realm.

N.B. Accidentally closed the issue, so reopened it again

Here's what the "wrapper property" approach looks like in Objective-C, along with a demonstration of setting and reading a flat array of strings to the realm object:

// Models
@interface RealmString : RLMObject
@property NSString *stringValue;
@end
@implementation RealmString
@end

RLM_ARRAY_TYPE(RealmString)

@interface TariffInfo : RLMObject
@property RLMArray<RealmString> *storedTariffInfoLines;
@property NSArray *tariffInfoLines;
@end
@implementation TariffInfo
+ (nullable NSArray *)ignoredProperties {
    return @[@"tariffInfoLines"];
}

- (void)setTariffInfoLines:(NSArray *)tariffInfoLines {
    [self.storedTariffInfoLines removeAllObjects];
    for (NSString *line in tariffInfoLines) {
        [self.storedTariffInfoLines addObject:[[RealmString alloc] initWithValue:@[line]]];
    }
}

- (NSArray *)tariffInfoLines {
    return [self.storedTariffInfoLines valueForKey:@"stringValue"];
}
@end

// Usage
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
    TariffInfo *info = [[TariffInfo alloc] init];
    // Set an array of strings
    info.tariffInfoLines = @[@"In this city you pay per minute."];
    [realm addObject:info];
}];
NSLog(@"Tariff Info: %@", [TariffInfo allObjects]);
// Prints
//
// Tariff Info: RLMResults <0x7fa7b0727310> (
//  [0] TariffInfo {
//      storedTariffInfoLines = RLMArray <0x7fa7b04ab760> (
//          [0] RealmString {
//              stringValue = In this city you pay per minute.;
//          }
//      );
//  }
// )
NSLog(@"Tariff Info Lines: %@", [[[TariffInfo allObjects] firstObject] tariffInfoLines]);
// Prints
//
// Tariff Info Lines: (
//  "In this city you pay per minute."
// )

I hope this helps!

Thanks for your help @jpsim. That has fixed my issue!

Glad to hear it!

i cannot seem to achieve this in Swift. I need List of Int type.

class OfficeTimingData:Object, Mappable{
dynamic var fromHour:String?
dynamic var toHour:String?

var days:[Int] // Not Working!!!!

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

func mapping(map: Map) {
    fromHour <- map["fromHour"]
    toHour <- map["toHour"]
    days <- map["days"]
}

}

@jpsim Thank you for your example.

@jpsim The first Swift example you wrote produces a compiler error with this message:

map produces [T], not the expected contextual result type List<RealmString>

However this seems, to go away if I forcibly cast it, e.g. as! List<RealmString>

Here's a revised version:

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 class func ignoredProperties() -> [String] {
        return ["nicknames"]
    }
}

Usage:

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

print(realm.objects(Person.self).first!.nicknames)
// Prints: ["Doug", "Dougie"]

Hello @jpsim
I made as you describe , but I get error "'RLMException', reason: 'Property 'confirmedMembersIds' is declared as 'NSArray', which is not a supported RLMObject property type. ", how can I fix it?

class RealmString: Object {

    dynamic var stringValue = ""

}

class CardModel: Object, Mappable {
    override static func ignoredProperties() -> [String] {
        return ["confirmedMembersIds, pendingRequests"]
    }

    var confirmedMembersIds: [String] {
        get {
            return _confirmedMembersIds.map { $0.stringValue }
        } set {
            _confirmedMembersIds.removeAll()
            _confirmedMembersIds.append(objectsIn: newValue.map { RealmString(value: [$0]) })
        }
    }
    private let _confirmedMembersIds = List<RealmString>()


    var pendingRequests: [String] {
        get {
            return _pendingRequests.map { $0.stringValue }
        } set {
            _pendingRequests.removeAll()
            _pendingRequests.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
        }
    }
    private let _pendingRequests = List<RealmString>()
}
override static func ignoredProperties() -> [String] {
    return ["confirmedMembersIds, pendingRequests"]
}

You're telling Realm to ignore a single property named confirmedMembersIds, pendingRequests, which isn't what you want. Your implementation should instead be:

return ["confirmedMembersIds", "pendingRequests"]

@bdash
Thank you very much

UPDATE:
We now support list of primitives. See https://realm.io/blog/realm-cocoa-reaches-3-0/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

matteodanelli picture matteodanelli  路  3Comments

javierjulio picture javierjulio  路  3Comments

ishidakei picture ishidakei  路  3Comments

dennisgec picture dennisgec  路  3Comments

i-schuetz picture i-schuetz  路  3Comments