Navigation

Embedded Objects

Overview

An embedded object is a special type of Realm object that models complex data about a specific object. Embedded objects are similar to relationships, but they provide additional constraints and map more naturally to the denormalized MongoDB document model.

Realm enforces unique ownership constraints that treat each embedded object as nested data inside of a single, specific parent object. An embedded object inherits the lifecycle of its parent object and cannot exist as an independent Realm object. Realm automatically deletes embedded objects if their parent object is deleted or when overwritten by a new embedded object instance.

Realm Uses Cascading Deletes for Embedded Objects

When you delete a Realm object, Realm automatically deletes any embedded objects referenced by that object. Any objects that your application must persist after the deletion of their parent object should use relationships instead.

Embedded Object Data Models

You can define embedded object types using either Realm object models or a server-side document schema. Embedded object types are reusable and composable. You can use the same embedded object type in multiple parent object types and you can embed objects inside of other embedded objects.

Important

Embedded objects cannot have a primary key.

Realm Object Models

To specify that a Realm object model define an embedded object, derive the embedded object class from EmbeddedObject in Swift or RLMEmbeddedObject in Objective-C. You can reference an embedded object type from parent object types in the same way as you would define a relationship:

// Define an embedded object
class Address: EmbeddedObject {
    @objc dynamic var street: String? = nil
    @objc dynamic var city: String? = nil
    @objc dynamic var country: String? = nil
    @objc dynamic var postalCode: String? = nil
}

// Define an object with one embedded object
class Contact: Object {
    @objc dynamic var _id = ObjectId.generate()
    @objc dynamic var name = ""
    
    // Embed a single object.
    // Embedded object properties must be marked optional. 
    @objc dynamic var address: Address? = nil
    
    override static func primaryKey() -> String? {
        return "_id"
    }
    
    convenience init(name: String, address: Address) {
        self.init()
        self.name = name
        self.address = address
    }    
}

// Define an object with an array of embedded objects
class Business: Object {
    @objc dynamic var _id = ObjectId.generate()
    @objc dynamic var name = ""
    let addresses = List<Address>() // Embed an array of objects
    
    override static func primaryKey() -> String? {
        return "_id"
    }
    
    convenience init(name: String, addresses: [Address]) {
        self.init()
        self.name = name
        self.addresses.append(objectsIn: addresses)
    }
}
// Define the embedded object
@interface Address : RLMEmbeddedObject
@property NSString *street;
@property NSString *city;
@property NSString *country;
@property NSString *postalCode;
@end

// Enable use in RLMArray
RLM_ARRAY_TYPE(Address)

@implementation Address
@end

// A Contact has one embedded address
@interface Contact : RLMObject
@property RLMObjectId *_id;
@property NSString *name;

// Define a single embedded object
@property Address *address;
@end

@implementation Contact
+ (NSString *)primaryKey {
    return @"_id";
}

+ (NSArray<NSString *> *)requiredProperties {
    return @[@"_id", @"name"];
}

+ (NSDictionary *)defaultPropertyValues {
    return @{@"_id": [RLMObjectId objectId]};
}

+ (instancetype)contactWithName:(NSString *)name {
    Contact *instance = [[Contact alloc] init];
    if (instance) {
        instance.name = name; 
    }
    return instance; 
}
@end

// A Business has multiple embedded addresses
@interface Business : RLMObject
@property RLMObjectId *_id;
@property NSString *name;
// Define an array of embedded objects
@property RLMArray<Address *><Address> *addresses;
@end

@implementation Business
+ (NSString *)primaryKey {
    return @"_id";
}

+ (NSArray<NSString *> *)requiredProperties {
    return @[@"_id", @"name"];
}
@end

JSON Schema

Unlike regular Realm objects, which map to their own MongoDB collection, embedded objects map to embedded documents in the parent type’s document schema:

{
  "title": "Contact",
  "bsonType": "object",
  "required": ["_id", "name", "address"],
  "properties": {
    "_id": "objectId",
    "name": "string",
    "address": {
      "title": "Address",
      "bsonType": "object",
      "properties": {
        "street": "string",
        "city": "string",
        "country": "string",
        "postalCode": "string"
      }
    }
  }
}
{
  "title": "Business",
  "bsonType": "object",
  "required": ["_id", "name", "addresses"],
  "properties": {
    "_id": "objectId",
    "name": "string",
    "addresses": {
      "bsonType": "array",
      "items": {
        "title": "Address",
        "bsonType": "object",
        "properties": {
          "street": "string",
          "city": "string",
          "country": "string",
          "postalCode": "string"
        }
      }
    }
  }
}

Read and Write Embedded Objects

Create an Embedded Object

To create an embedded object, assign an instance of the embedded object to a parent object’s property:

let realm = try! Realm()

try! realm.write {
    let address = Address()
    address.street = "123 Fake St."
    address.city = "Springfield"
    address.country = "USA"
    address.postalCode = "90710"
    let contact = Contact(name: "Nick Riviera", address: address)
    realm.add(contact)
}
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
    Address *address = [[Address alloc] init];
    address.street = @"123 Fake St.";
    address.city = @"Springfield";
    address.country = @"USA";
    address.postalCode = @"90710";
    
    Contact *contact = [Contact contactWithName:@"Nick Riviera"];
    
    // Assign the embedded object property
    contact.address = address;

    [realm addObject:contact];
    
    NSLog(@"Added contact: %@", contact);
}];

Update an Embedded Object Property

To update a property in an embedded object, modify the property in a write transaction:

let id = ObjectId("5f47f4811060b1aa6cc71272") // An arbitrary id
try! realm.write {
    guard let contact = realm.object(ofType: Contact.self, forPrimaryKey: id) else {
        print("Contact not found")
        return
    }
    contact.address?.street = "Hollywood Upstairs Medical College"
    contact.address?.city = "Los Angeles"
    contact.address?.postalCode = "90210"
    print("Updated contact: \(contact)")
}
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock: ^{
    Contact *contact = [Contact objectInRealm:realm
                                forPrimaryKey:[[RLMObjectId alloc] initWithString:@"5f481c21f634a1f4eeaa7268" error:nil]];
    contact.address.street = @"Hollywood Upstairs Medical College";
    contact.address.city = @"Los Angeles";
    contact.address.postalCode = @"90210";
    NSLog(@"Updated contact: %@", contact);
}];

Overwrite an Embedded Object

To overwrite an embedded object, reassign the embedded object property of a party to a new instance in a write transaction:

let id = ObjectId("5f47f4811060b1aa6cc71272") // An arbitrary id
try! realm.write {
    guard let contact = realm.object(ofType: Contact.self, forPrimaryKey: id) else {
        print("Contact not found")
        return
    }
    let newAddress = Address()
    newAddress.street = "Hollywood Upstairs Medical College"
    newAddress.city = "Los Angeles"
    newAddress.country = "USA"
    newAddress.postalCode = "90210"
    contact.address = newAddress
    print("Updated contact: \(contact)")
}
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock: ^{
    Contact *contact = [Contact objectInRealm:realm
                                forPrimaryKey:[[RLMObjectId alloc] initWithString:@"5f481c21f634a1f4eeaa7268" error:nil]];
    Address *newAddress = [[Address alloc] init];
    newAddress.street = @"Hollywood Upstairs Medical College";
    newAddress.city = @"Los Angeles";
    newAddress.country = @"USA";
    newAddress.postalCode = @"90210";
    contact.address = newAddress;
    NSLog(@"Updated contact: %@", contact);
}];

Query a Collection on Embedded Object Properties

Use dot notation to filter or sort a collection of objects based on an embedded object property value:

Note

It is not possible to query embedded objects directly. Instead, access embedded objects through a query for the parent object type.

let losAngelesContacts = realm.objects(Contact.self)
    .filter("address.city = %@", "Los Angeles")
    .sorted(byKeyPath: "address.street")
print("Los Angeles Contacts: \(losAngelesContacts)")
RLMRealm *realm = [RLMRealm defaultRealm];
RLMResults<Contact *> *losAngelesContacts = [Contact objectsInRealm:realm where:@"address.city = %@", @"Los Angeles"];

losAngelesContacts = [losAngelesContacts sortedResultsUsingKeyPath:@"address.street" ascending:YES]; 
NSLog(@"Los Angeles Contacts: %@", losAngelesContacts);