diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c2df6d630..7ebe1dcf6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +x.x.x Release notes (yyyy-MM-dd) +============================================================= +### Enhancements +* None. + +### Fixed +* Fixed an issue in `toJSON()`, in combination with primaryKeys, where data from another table could be returned. ([#3331](https://github.com/realm/realm-js/issues/3331), since v10.0.0) +* Fixed an issue in `toJSON()` where `data` would output as `{}`, it now returns the data base64 encoded. ([#3356](https://github.com/realm/realm-js/pull/3356), since v10.0.0) + +### Compatibility +* MongoDB Realm Cloud. +* APIs are backwards compatible with all previous releases of Realm JavaScript in the 10.x.y series. +* File format: generates Realms with format v20 (reads and upgrades file format v5 or later for non-synced Realm, upgrades file format v10 for synced Realms). + +### Internal +* None. + 10.0.1 Release notes (2020-10-16) ============================================================== NOTE: Support for syncing with realm.cloud.io and/or Realm Object Server has been replaced with support for syncing with MongoDB Realm Cloud. diff --git a/integration-tests/tests/src/schemas/person-and-dog-with-object-ids.ts b/integration-tests/tests/src/schemas/person-and-dog-with-object-ids.ts index 4b8bd78dd1..d7690896ed 100755 --- a/integration-tests/tests/src/schemas/person-and-dog-with-object-ids.ts +++ b/integration-tests/tests/src/schemas/person-and-dog-with-object-ids.ts @@ -16,8 +16,10 @@ // //////////////////////////////////////////////////////////////////////////// +/* tslint:disable max-classes-per-file */ + import * as Realm from "realm"; -import { ObjectId } from 'bson' +import { ObjectId } from "bson"; export interface IPerson { _id: ObjectId; @@ -34,11 +36,11 @@ export const PersonSchema: Realm.ObjectSchema = { _id: "objectId", age: "int", name: "string", - friends: "Person[]" - } + friends: "Person[]", + }, }; -export class Person extends Realm.Object { +export class Person extends Realm.Object implements IPerson { _id: ObjectId; name: string; age: number; @@ -62,11 +64,11 @@ export const DogSchema: Realm.ObjectSchema = { _id: "objectId", age: "int", name: "string", - owner: "Person" - } + owner: "Person", + }, }; -export class Dog extends Realm.Object { +export class Dog extends Realm.Object implements IDog { _id: ObjectId; name: string; age: number; diff --git a/integration-tests/tests/src/schemas/person-and-dogs.ts b/integration-tests/tests/src/schemas/person-and-dogs.ts index 6927bcf463..652711d312 100644 --- a/integration-tests/tests/src/schemas/person-and-dogs.ts +++ b/integration-tests/tests/src/schemas/person-and-dogs.ts @@ -16,6 +16,8 @@ // //////////////////////////////////////////////////////////////////////////// +/* tslint:disable max-classes-per-file */ + import * as Realm from "realm"; export interface IPerson { @@ -30,8 +32,8 @@ export const PersonSchema: Realm.ObjectSchema = { properties: { age: "int", name: "string", - friends: "Person[]" - } + friends: "Person[]", + }, }; export class Person extends Realm.Object { @@ -41,10 +43,10 @@ export class Person extends Realm.Object { dogs: Realm.Collection; constructor(name: string, age: number) { - super(); + super(); - this.name = name; - this.age = age; + this.name = name; + this.age = age; } static schema: Realm.ObjectSchema = PersonSchema; @@ -61,8 +63,8 @@ export const DogSchema: Realm.ObjectSchema = { properties: { age: "int", name: "string", - owner: "Person" - } + owner: "Person", + }, }; export class Dog extends Realm.Object { @@ -71,11 +73,11 @@ export class Dog extends Realm.Object { owner: Person; constructor(name: string, age: number, owner: Person) { - super(); + super(); - this.name = name; - this.age = age; - this.owner = owner; + this.name = name; + this.age = age; + this.owner = owner; } static schema: Realm.ObjectSchema = DogSchema; diff --git a/integration-tests/tests/src/schemas/playlist-with-songs-with-ids.ts b/integration-tests/tests/src/schemas/playlist-with-songs-with-ids.ts new file mode 100644 index 0000000000..1851d92863 --- /dev/null +++ b/integration-tests/tests/src/schemas/playlist-with-songs-with-ids.ts @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +import * as Realm from "realm"; + +/* tslint:disable max-classes-per-file */ + +export interface IPlaylist { + _id: number; + title: string; + songs: Realm.List; + related: Realm.List; +} + +export const PlaylistSchema: Realm.ObjectSchema = { + name: "Playlist", + primaryKey: "_id", + properties: { + _id: "int", + title: "string", + songs: "Song[]", + related: "Playlist[]", + }, +}; + +export class Playlist extends Realm.Object implements IPlaylist { + _id: number; + title: string; + songs: Realm.List; + related: Realm.List; + + static schema = PlaylistSchema; +} + +export interface ISong { + _id: number; + artist: string; + title: string; +} + +export const SongSchema: Realm.ObjectSchema = { + name: "Song", + primaryKey: "_id", + properties: { + _id: "int", + artist: "string", + title: "string", + }, +}; + +export class Song extends Realm.Object implements ISong { + _id: number; + artist: string; + title: string; + + static schema = SongSchema; +} diff --git a/integration-tests/tests/src/schemas/playlist-with-songs.ts b/integration-tests/tests/src/schemas/playlist-with-songs.ts new file mode 100644 index 0000000000..99c0dec5da --- /dev/null +++ b/integration-tests/tests/src/schemas/playlist-with-songs.ts @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2020 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +import * as Realm from "realm"; + +/* tslint:disable max-classes-per-file */ + +export interface IPlaylist { + title: string; + songs: Realm.List; + related: Realm.List; +} + +export const PlaylistSchema: Realm.ObjectSchema = { + name: "Playlist", + properties: { + title: "string", + songs: "Song[]", + related: "Playlist[]", + }, +}; + +export class Playlist extends Realm.Object implements IPlaylist { + title: string; + songs: Realm.List; + related: Realm.List; + + static schema = PlaylistSchema; +} + +export interface ISong { + artist: string; + title: string; +} + +export const SongSchema: Realm.ObjectSchema = { + name: "Song", + properties: { + artist: "string", + title: "string", + }, +}; + +export class Song extends Realm.Object implements ISong { + artist: string; + title: string; + + static schema = SongSchema; +} diff --git a/integration-tests/tests/src/serialization.ts b/integration-tests/tests/src/serialization.ts index c4d7bc8fbe..8796ec09ca 100755 --- a/integration-tests/tests/src/serialization.ts +++ b/integration-tests/tests/src/serialization.ts @@ -17,24 +17,25 @@ //////////////////////////////////////////////////////////////////////////// import { expect } from "chai"; - +import { ObjectId } from "bson"; import { - IPerson, - PersonSchema, - DogSchema, - Person, - Dog, -} from "./schemas/person-and-dogs"; + IPlaylist as IPlaylistNoId, + ISong as ISongNoId, + PlaylistSchema as PlaylistSchemaNoId, + SongSchema as SongSchemaNoId, + Playlist as PlaylistNoId, + Song as SongNoId, +} from "./schemas/playlist-with-songs"; import { - IPerson as IPersonWithId, - PersonSchema as PersonSchemaWithId, - DogSchema as DogSchemaWithId, - Person as PersonWithId, - Dog as DogWithId, -} from "./schemas/person-and-dog-with-object-ids"; + IPlaylist as IPlaylistWithId, + ISong as ISongWithId, + PlaylistSchema as PlaylistSchemaWithId, + SongSchema as SongSchemaWithId, + Playlist as PlaylistWithId, + Song as SongWithId, +} from "./schemas/playlist-with-songs-with-ids"; import * as circularCollectionResult from "./structures/circular-collection-result.json"; -import * as circularCollectionResultWithIds from "./structures/circular-collection-result-with-object-ids.json"; -import { ObjectId } from "bson"; +import * as circularCollectionResultWithIds from "./structures/circular-collection-result-with-primary-ids.json"; describe("JSON serialization (exposed properties)", () => { it("JsonSerializationReplacer is exposed on the Realm constructor", () => { @@ -51,38 +52,104 @@ type TestSetup = { }; }; +interface ICacheIdTestSetup { + type: string; + schemaName: string; + testId: any; + expectedResult: string; +} + +/** + * Create test data (TestSetups) in 4 ways, with the same data structure: + * 1. Literals without primaryKeys + * 2. Class Models without primaryKeys + * 3. Literals with primaryKeys + * 4. Class Models with primaryKeys + */ const testSetups: TestSetup[] = [ { - name: "Object literal", + name: "Object literal (NO primaryKey)", testData: () => { const realm = new Realm({ - schema: [PersonSchema, DogSchema], inMemory: true, + schema: [PlaylistSchemaNoId, SongSchemaNoId], }); realm.write(() => { - const john = realm.create(PersonSchema.name, { - name: "John Doe", - age: 42, + // Shared songs + const s1 = realm.create(SongSchemaNoId.name, { + artist: "Shared artist name 1", + title: "Shared title name 1", }); - const jane = realm.create(PersonSchema.name, { - name: "Jane Doe", - age: 40, + const s2 = realm.create(SongSchemaNoId.name, { + artist: "Shared artist name 2", + title: "Shared title name 2", }); - const tony = realm.create(PersonSchema.name, { - name: "Tony Doe", - age: 35, + const s3 = realm.create(SongSchemaNoId.name, { + artist: "Shared artist name 3", + title: "Shared title name 3", }); - // ensure circular references - john.friends.push(john); - john.friends.push(jane); - john.friends.push(tony); - - jane.friends.push(tony); - jane.friends.push(john); + // Playlists + const p1 = realm.create( + PlaylistSchemaNoId.name, + { + title: "Playlist 1", + songs: [ + s1, + s2, + s3, + { + artist: "Unique artist 1", + title: "Unique title 1", + }, + { + artist: "Unique artist 2", + title: "Unique title 2", + }, + ], + } + ); + const p2 = realm.create( + PlaylistSchemaNoId.name, + { + title: "Playlist 2", + songs: [ + { + artist: "Unique artist 3", + title: "Unique title 3", + }, + { + artist: "Unique artist 4", + title: "Unique title 4", + }, + s3, + ], + related: [p1], + } + ); + const p3 = realm.create( + PlaylistSchemaNoId.name, + { + title: "Playlist 3", + songs: [ + s1, + { + artist: "Unique artist 5", + title: "Unique title 5", + }, + { + artist: "Unique artist 6", + title: "Unique title 6", + }, + s2, + ], + related: [p1, p2], + } + ); - tony.friends.push(jane); + // ensure circular references for p1 (ensure p1 reference self fist) + p1.related.push(p1, p2, p3); // test self reference }); return { @@ -92,33 +159,61 @@ const testSetups: TestSetup[] = [ }, }, { - name: "Class models", + name: "Class models (NO primaryKey)", testData: () => { - const realm = new Realm({ schema: [Person, Dog], inMemory: true }); + const realm = new Realm({ + inMemory: true, + schema: [PlaylistNoId, SongNoId], + }); realm.write(() => { - const john = realm.create(Person, { - name: "John Doe", - age: 42, + // Shared songs + const s1 = realm.create(SongNoId, { + artist: "Shared artist name 1", + title: "Shared title name 1", }); - const jane = realm.create(Person, { - name: "Jane Doe", - age: 40, + const s2 = realm.create(SongNoId, { + artist: "Shared artist name 2", + title: "Shared title name 2", }); - const tony = realm.create(Person, { - name: "Tony Doe", - age: 35, + const s3 = realm.create(SongNoId, { + artist: "Shared artist name 3", + title: "Shared title name 3", }); - // ensure circular references - john.friends.push(john); - john.friends.push(jane); - john.friends.push(tony); - - jane.friends.push(tony); - jane.friends.push(john); + // Playlists + const p1 = realm.create(PlaylistNoId, { + title: "Playlist 1", + songs: [ + s1, + s2, + s3, + { artist: "Unique artist 1", title: "Unique title 1" }, + { artist: "Unique artist 2", title: "Unique title 2" }, + ], + }); + const p2 = realm.create(PlaylistNoId, { + title: "Playlist 2", + songs: [ + { artist: "Unique artist 3", title: "Unique title 3" }, + { artist: "Unique artist 4", title: "Unique title 4" }, + s3, + ], + related: [p1], + }); + const p3 = realm.create(PlaylistNoId, { + title: "Playlist 3", + songs: [ + s1, + { artist: "Unique artist 5", title: "Unique title 5" }, + { artist: "Unique artist 6", title: "Unique title 6" }, + s2, + ], + related: [p1, p2], + }); - tony.friends.push(jane); + // ensure circular references for p1 (ensure p1 reference self fist) + p1.related.push(p1, p2, p3); // test self reference }); return { @@ -128,48 +223,100 @@ const testSetups: TestSetup[] = [ }, }, { - name: "Object literal with primary ObjectId", + name: "Object literal (Int primaryKey)", testData: () => { const realm = new Realm({ - schema: [PersonSchemaWithId, DogSchemaWithId], inMemory: true, + schema: [PlaylistSchemaWithId, SongSchemaWithId], }); realm.write(() => { - const john = realm.create( - PersonSchemaWithId.name, + // Shared songs + const s1 = realm.create(SongSchemaWithId.name, { + _id: 1, + artist: "Shared artist name 1", + title: "Shared title name 1", + }); + const s2 = realm.create(SongSchemaWithId.name, { + _id: 2, + artist: "Shared artist name 2", + title: "Shared title name 2", + }); + const s3 = realm.create(SongSchemaWithId.name, { + _id: 3, + artist: "Shared artist name 3", + title: "Shared title name 3", + }); + + // Playlists + const p1 = realm.create( + PlaylistSchemaWithId.name, { - _id: new ObjectId("5f086d00ddf69c48082eb63b"), - name: "John Doe", - age: 42, + _id: 1, + title: "Playlist 1", + songs: [ + s1, + s2, + s3, + { + _id: 4, + artist: "Unique artist 1", + title: "Unique title 1", + }, + { + _id: 5, + artist: "Unique artist 2", + title: "Unique title 2", + }, + ], } ); - const jane = realm.create( - PersonSchemaWithId.name, + const p2 = realm.create( + PlaylistSchemaWithId.name, { - _id: new ObjectId("5f086d00ddf69c48082eb63d"), - name: "Jane Doe", - age: 40, + _id: 2, + title: "Playlist 2", + songs: [ + { + _id: 6, + artist: "Unique artist 3", + title: "Unique title 3", + }, + { + _id: 7, + artist: "Unique artist 4", + title: "Unique title 4", + }, + s3, + ], + related: [p1], } ); - const tony = realm.create( - PersonSchemaWithId.name, + const p3 = realm.create( + PlaylistSchemaWithId.name, { - _id: new ObjectId("5f086d00ddf69c48082eb63f"), - name: "Tony Doe", - age: 35, + _id: 3, + title: "Playlist 3", + songs: [ + s1, + { + _id: 8, + artist: "Unique artist 5", + title: "Unique title 5", + }, + { + _id: 9, + artist: "Unique artist 6", + title: "Unique title 6", + }, + s2, + ], + related: [p1, p2], } ); - // ensure circular references - john.friends.push(john); - john.friends.push(jane); - john.friends.push(tony); - - jane.friends.push(tony); - jane.friends.push(john); - - tony.friends.push(jane); + // ensure circular references for p1 (ensure p1 reference self fist) + p1.related.push(p1, p2, p3); // test self reference }); return { @@ -179,39 +326,91 @@ const testSetups: TestSetup[] = [ }, }, { - name: "Class models with primary ObjectId", + name: "Class models (Int primaryKey)", testData: () => { const realm = new Realm({ - schema: [PersonWithId, DogWithId], inMemory: true, + schema: [PlaylistWithId, SongWithId], }); realm.write(() => { - const john = realm.create(PersonWithId, { - _id: new ObjectId("5f086d00ddf69c48082eb63b"), - name: "John Doe", - age: 42, + // Shared songs + const s1 = realm.create(SongWithId, { + _id: 1, + artist: "Shared artist name 1", + title: "Shared title name 1", }); - const jane = realm.create(PersonWithId, { - _id: new ObjectId("5f086d00ddf69c48082eb63d"), - name: "Jane Doe", - age: 40, + const s2 = realm.create(SongWithId, { + _id: 2, + artist: "Shared artist name 2", + title: "Shared title name 2", }); - const tony = realm.create(PersonWithId, { - _id: new ObjectId("5f086d00ddf69c48082eb63f"), - name: "Tony Doe", - age: 35, + const s3 = realm.create(SongWithId, { + _id: 3, + artist: "Shared artist name 3", + title: "Shared title name 3", }); - // ensure circular references - john.friends.push(john); - john.friends.push(jane); - john.friends.push(tony); - - jane.friends.push(tony); - jane.friends.push(john); + // Playlists + const p1 = realm.create(PlaylistWithId, { + _id: 1, + title: "Playlist 1", + songs: [ + s1, + s2, + s3, + { + _id: 4, + artist: "Unique artist 1", + title: "Unique title 1", + }, + { + _id: 5, + artist: "Unique artist 2", + title: "Unique title 2", + }, + ], + }); + const p2 = realm.create(PlaylistWithId, { + _id: 2, + title: "Playlist 2", + songs: [ + { + _id: 6, + artist: "Unique artist 3", + title: "Unique title 3", + }, + { + _id: 7, + artist: "Unique artist 4", + title: "Unique title 4", + }, + s3, + ], + related: [p1], + }); + const p3 = realm.create(PlaylistWithId, { + _id: 3, + title: "Playlist 3", + songs: [ + s1, + { + _id: 8, + artist: "Unique artist 5", + title: "Unique title 5", + }, + { + _id: 9, + artist: "Unique artist 6", + title: "Unique title 6", + }, + s2, + ], + related: [p1, p2], + }); - tony.friends.push(jane); + // ensure circular references for p1 (ensure p1 reference self fist) + p1.related.push(p1, p2, p3); // test self reference }); return { @@ -222,88 +421,211 @@ const testSetups: TestSetup[] = [ }, ]; +const cacheIdTestSetups: ICacheIdTestSetup[] = [ + { + type: "int", + schemaName: "IntIdTest", + testId: 1337, + expectedResult: "IntIdTest#1337", + }, + { + type: "string", + schemaName: "StringIdTest", + testId: + "~!@#$%^&*()_+=-,./<>? 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ abcdefghijklmnopqrstuvwxyzæøå", + expectedResult: + "StringIdTest#~!@#$%^&*()_+=-,./<>? 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ abcdefghijklmnopqrstuvwxyzæøå", + }, + { + type: "objectId", + schemaName: "ObjectIdTest", + testId: new ObjectId("5f99418846da9c45005f50bf"), + expectedResult: "ObjectIdTest#5f99418846da9c45005f50bf", + }, +]; + describe("JSON serialization", () => { - let testSetup: TestSetup; - let realm: Realm | null; - let predefinedStructure: any; - let persons: Realm.Results; - - beforeEach(() => { - ({ realm, predefinedStructure } = testSetup.testData()); - persons = realm.objects(PersonSchema.name).sorted("age", true); - }); + describe(`Internal cache id check for types: ${cacheIdTestSetups + .map((t) => t.type) + .join(" / ")}`, () => { + for (const test of cacheIdTestSetups) { + const { type, schemaName, testId, expectedResult } = test; + + it(`generates correct cache id for primaryKey type: ${type}`, () => { + const realm = new Realm({ + inMemory: true, + schema: [ + { + name: schemaName, + primaryKey: "_id", + properties: { + _id: type, + title: "string", + }, + }, + ], + }); - afterEach(() => { - if (realm) { - realm.write(() => { - realm.deleteAll(); + realm.write(() => { + realm.create(schemaName, { + _id: testId, + title: `Cache id should be: ${expectedResult}`, + }); + }); + + const testSubject = realm.objectForPrimaryKey( + schemaName, + testId + ); + const json = JSON.stringify( + testSubject, + Realm.JsonSerializationReplacer + ); + const parsed = JSON.parse(json); + + expect(parsed.$refId).equals(expectedResult); + + realm.close(); }); - realm.close(); - realm = null; } }); - testSetups.forEach((ts) => { - // expose testSetup to predefined before/after hooks - testSetup = ts; + for (const ts of testSetups) { + const testSetup = ts; describe(`Repeated test for "${testSetup.name}":`, () => { + let realm: Realm | null; + let predefinedStructure: any; + let playlists: Realm.Results; + + beforeEach(() => { + ({ realm, predefinedStructure } = testSetup.testData()); + playlists = realm + .objects(PlaylistSchemaNoId.name) + .sorted("title"); + }); + + afterEach(() => { + if (realm) { + realm.write(() => { + realm.deleteAll(); + }); + realm.close(); + realm = null; + } + }); + describe("Realm.Object", () => { + it("extends Realm.Object", () => { + // Check that entries in the result set extends Realm.Object. + expect(playlists[0]).instanceOf(Realm.Object); + }); + it("implements toJSON", () => { - expect(typeof persons[0].toJSON).equals("function"); + // Check that fist Playlist has toJSON implemented. + expect(typeof playlists[0].toJSON).equals("function"); }); it("toJSON returns a circular structure", () => { - const serializable = persons[0].toJSON(); + const serializable = playlists[0].toJSON(); + + // Check that no props are functions on the serializable object. + expect( + Object.values(serializable).some( + (val) => typeof val === "function" + ) + ).equals(false); - expect(serializable.objectSchema).equals(undefined); - expect(serializable.friends).instanceOf(Array); - expect(serializable).equals(serializable.friends[0]); + // Check that linked list is not a Realm entity. + expect(serializable.related).not.instanceOf( + Realm.Collection + ); + // But is a plain Array + expect(Array.isArray(serializable.related)).equals(true); + + // Check that the serializable object is the same as the first related object. + // (this check only makes sense because of our structure) + expect(serializable).equals(serializable.related[0]); }); it("throws correct error on serialization", () => { - expect(() => JSON.stringify(persons[0])).throws( + // Check that we get a circular structure error. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value + expect(() => JSON.stringify(playlists[0])).throws( TypeError, /circular|cyclic/i ); }); it("serializes to expected output using Realm.JsonSerializationReplacer", () => { - expect( - JSON.stringify( - persons[0], - Realm.JsonSerializationReplacer - ) - ).equals(JSON.stringify(predefinedStructure[0])); + const json = JSON.stringify( + playlists[0], + Realm.JsonSerializationReplacer + ); + const generated = JSON.parse(json); + + // Check that we get the expected structure. + // (parsing back to an object & using deep equals, as we can't rely on property order) + expect(generated).deep.equals(predefinedStructure[0]); }); }); describe("Realm.Results", () => { + it("extends Realm.Collection", () => { + // Check that the result set extends Realm.Collection. + expect(playlists).instanceOf(Realm.Collection); + }); + it("implements toJSON", () => { - expect(typeof persons.toJSON).equals("function"); + expect(typeof playlists.toJSON).equals("function"); }); it("toJSON returns a circular structure", () => { - const serializable = persons.toJSON(); + const serializable = playlists.toJSON(); + + // Check that the serializable object is not a Realm entity. + expect(serializable).not.instanceOf(Realm.Collection); + // But is a plain Array + expect(Array.isArray(serializable)).equals(true); + + // Check that the serializable object is not a Realm entity. + expect(serializable).not.instanceOf(Realm.Collection); + // But is a plain Array + expect(Array.isArray(serializable)).equals(true); + + // Check that linked list is not a Realm entity. + expect(serializable[0].related).not.instanceOf( + Realm.Collection + ); + // But is a plain Array + expect(Array.isArray(serializable[0].related)).equals(true); - expect(serializable).instanceOf(Array); - expect(serializable[0].friends).instanceOf(Array); - expect(serializable[0]).equals(serializable[0].friends[0]); + // Check that the serializable object is the same as the first related object. + // (this check only makes sense because of our structure) + expect(serializable[0]).equals(serializable[0].related[0]); }); it("throws correct error on serialization", () => { - expect(() => JSON.stringify(persons)).throws( + // Check that we get a circular structure error. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value + expect(() => JSON.stringify(playlists)).throws( TypeError, /circular|cyclic/i ); }); it("serializes to expected output using Realm.JsonSerializationReplacer", () => { - expect( - JSON.stringify(persons, Realm.JsonSerializationReplacer) - ).equals(JSON.stringify(predefinedStructure)); + const json = JSON.stringify( + playlists, + Realm.JsonSerializationReplacer + ); + const generated = JSON.parse(json); + + // Check that we get the expected structure. + // (parsing back to an object & using deep equals, as we can't rely on property order) + expect(generated).deep.equals(predefinedStructure); }); }); }); - }); + } }); diff --git a/integration-tests/tests/src/structures/circular-collection-result-with-object-ids.json b/integration-tests/tests/src/structures/circular-collection-result-with-object-ids.json deleted file mode 100755 index c8e88bc9ab..0000000000 --- a/integration-tests/tests/src/structures/circular-collection-result-with-object-ids.json +++ /dev/null @@ -1,125 +0,0 @@ -[ - { - "_id": "5f086d00ddf69c48082eb63b", - "age": 42, - "name": "John Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63b" - }, - { - "_id": "5f086d00ddf69c48082eb63d", - "age": 40, - "name": "Jane Doe", - "friends": [ - { - "_id": "5f086d00ddf69c48082eb63f", - "age": 35, - "name": "Tony Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63d" - } - ] - }, - { - "$ref": "5f086d00ddf69c48082eb63b" - } - ] - }, - { - "_id": "5f086d00ddf69c48082eb63f", - "age": 35, - "name": "Tony Doe", - "friends": [ - { - "_id": "5f086d00ddf69c48082eb63d", - "age": 40, - "name": "Jane Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63f" - }, - { - "$ref": "5f086d00ddf69c48082eb63b" - } - ] - } - ] - } - ] - }, - { - "_id": "5f086d00ddf69c48082eb63d", - "age": 40, - "name": "Jane Doe", - "friends": [ - { - "_id": "5f086d00ddf69c48082eb63f", - "age": 35, - "name": "Tony Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63d" - } - ] - }, - { - "_id": "5f086d00ddf69c48082eb63b", - "age": 42, - "name": "John Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63b" - }, - { - "$ref": "5f086d00ddf69c48082eb63d" - }, - { - "_id": "5f086d00ddf69c48082eb63f", - "age": 35, - "name": "Tony Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63d" - } - ] - } - ] - } - ] - }, - { - "_id": "5f086d00ddf69c48082eb63f", - "age": 35, - "name": "Tony Doe", - "friends": [ - { - "_id": "5f086d00ddf69c48082eb63d", - "age": 40, - "name": "Jane Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63f" - }, - { - "_id": "5f086d00ddf69c48082eb63b", - "age": 42, - "name": "John Doe", - "friends": [ - { - "$ref": "5f086d00ddf69c48082eb63b" - }, - { - "$ref": "5f086d00ddf69c48082eb63d" - }, - { - "$ref": "5f086d00ddf69c48082eb63f" - } - ] - } - ] - } - ] - } -] diff --git a/integration-tests/tests/src/structures/circular-collection-result-with-primary-ids.json b/integration-tests/tests/src/structures/circular-collection-result-with-primary-ids.json new file mode 100755 index 0000000000..1a5e90377d --- /dev/null +++ b/integration-tests/tests/src/structures/circular-collection-result-with-primary-ids.json @@ -0,0 +1,434 @@ +[ + { + "$refId": "Playlist#1", + "_id": 1, + "title": "Playlist 1", + "songs": [ + { + "$refId": "Song#1", + "_id": 1, + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#2", + "_id": 2, + "artist": "Shared artist name 2", + "title": "Shared title name 2" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#4", + "_id": 4, + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#5", + "_id": 5, + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ + { + "$ref": "Playlist#1" + }, + { + "$refId": "Playlist#2", + "_id": 2, + "title": "Playlist 2", + "songs": [ + { + "$refId": "Song#6", + "_id": 6, + "artist": "Unique artist 3", + "title": "Unique title 3" + }, + { + "$refId": "Song#7", + "_id": 7, + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$ref": "Playlist#1" + } + ] + }, + { + "$refId": "Playlist#3", + "_id": 3, + "title": "Playlist 3", + "songs": [ + { + "$refId": "Song#1", + "_id": 1, + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#8", + "_id": 8, + "artist": "Unique artist 5", + "title": "Unique title 5" + }, + { + "$refId": "Song#9", + "_id": 9, + "artist": "Unique artist 6", + "title": "Unique title 6" + }, + { + "$refId": "Song#2", + "_id": 2, + "artist": "Shared artist name 2", + "title": "Shared title name 2" + } + ], + "related": [ + { + "$ref": "Playlist#1" + }, + { + "$refId": "Playlist#2", + "_id": 2, + "title": "Playlist 2", + "songs": [ + { + "$refId": "Song#6", + "_id": 6, + "artist": "Unique artist 3", + "title": "Unique title 3" + }, + { + "$refId": "Song#7", + "_id": 7, + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$ref": "Playlist#1" + } + ] + } + ] + } + ] + }, + { + "$refId": "Playlist#2", + "_id": 2, + "title": "Playlist 2", + "songs": [ + { + "$refId": "Song#6", + "_id": 6, + "artist": "Unique artist 3", + "title": "Unique title 3" + }, + { + "$refId": "Song#7", + "_id": 7, + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$refId": "Playlist#1", + "_id": 1, + "title": "Playlist 1", + "songs": [ + { + "$refId": "Song#1", + "_id": 1, + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#2", + "_id": 2, + "artist": "Shared artist name 2", + "title": "Shared title name 2" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#4", + "_id": 4, + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#5", + "_id": 5, + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ + { + "$ref": "Playlist#1" + }, + { + "$ref": "Playlist#2" + }, + { + "$refId": "Playlist#3", + "_id": 3, + "title": "Playlist 3", + "songs": [ + { + "$refId": "Song#1", + "_id": 1, + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#8", + "_id": 8, + "artist": "Unique artist 5", + "title": "Unique title 5" + }, + { + "$refId": "Song#9", + "_id": 9, + "artist": "Unique artist 6", + "title": "Unique title 6" + }, + { + "$refId": "Song#2", + "_id": 2, + "artist": "Shared artist name 2", + "title": "Shared title name 2" + } + ], + "related": [ + { + "$ref": "Playlist#1" + }, + { + "$ref": "Playlist#2" + } + ] + } + ] + } + ] + }, + { + "$refId": "Playlist#3", + "_id": 3, + "title": "Playlist 3", + "songs": [ + { + "$refId": "Song#1", + "_id": 1, + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#8", + "_id": 8, + "artist": "Unique artist 5", + "title": "Unique title 5" + }, + { + "$refId": "Song#9", + "_id": 9, + "artist": "Unique artist 6", + "title": "Unique title 6" + }, + { + "$refId": "Song#2", + "_id": 2, + "artist": "Shared artist name 2", + "title": "Shared title name 2" + } + ], + "related": [ + { + "$refId": "Playlist#1", + "_id": 1, + "title": "Playlist 1", + "songs": [ + { + "$refId": "Song#1", + "_id": 1, + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#2", + "_id": 2, + "artist": "Shared artist name 2", + "title": "Shared title name 2" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#4", + "_id": 4, + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#5", + "_id": 5, + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ + { + "$ref": "Playlist#1" + }, + { + "$refId": "Playlist#2", + "_id": 2, + "title": "Playlist 2", + "songs": [ + { + "$refId": "Song#6", + "_id": 6, + "artist": "Unique artist 3", + "title": "Unique title 3" + }, + { + "$refId": "Song#7", + "_id": 7, + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$ref": "Playlist#1" + } + ] + }, + { + "$ref": "Playlist#3" + } + ] + }, + { + "$refId": "Playlist#2", + "_id": 2, + "title": "Playlist 2", + "songs": [ + { + "$refId": "Song#6", + "_id": 6, + "artist": "Unique artist 3", + "title": "Unique title 3" + }, + { + "$refId": "Song#7", + "_id": 7, + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$refId": "Playlist#1", + "_id": 1, + "title": "Playlist 1", + "songs": [ + { + "$refId": "Song#1", + "_id": 1, + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#2", + "_id": 2, + "artist": "Shared artist name 2", + "title": "Shared title name 2" + }, + { + "$refId": "Song#3", + "_id": 3, + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#4", + "_id": 4, + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#5", + "_id": 5, + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ + { + "$ref": "Playlist#1" + }, + { + "$ref": "Playlist#2" + }, + { + "$ref": "Playlist#3" + } + ] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/integration-tests/tests/src/structures/circular-collection-result.json b/integration-tests/tests/src/structures/circular-collection-result.json index 926f56faa4..e8711eae7e 100644 --- a/integration-tests/tests/src/structures/circular-collection-result.json +++ b/integration-tests/tests/src/structures/circular-collection-result.json @@ -1,47 +1,116 @@ [ { - "$refId": "Person{0000-0000}", - "age": 42, - "name": "John Doe", - "friends": [ + "$refId": "Playlist#{0000-0000}", + "title": "Playlist 1", + "songs": [ { - "$ref": "Person{0000-0000}" + "$refId": "Song#{0000-0000}", + "artist": "Shared artist name 1", + "title": "Shared title name 1" }, { - "$refId": "Person{0000-0001}", - "age": 40, - "name": "Jane Doe", - "friends": [ + "$refId": "Song#{0000-0001}", + "artist": "Shared artist name 2", + "title": "Shared title name 2" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#{0000-0003}", + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#{0000-0004}", + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" + }, + { + "$refId": "Playlist#{0000-0001}", + "title": "Playlist 2", + "songs": [ { - "$refId": "Person{0000-0002}", - "age": 35, - "name": "Tony Doe", - "friends": [ - { - "$ref": "Person{0000-0001}" - } - ] + "$refId": "Song#{0000-0005}", + "artist": "Unique artist 3", + "title": "Unique title 3" }, { - "$ref": "Person{0000-0000}" + "$refId": "Song#{0000-0006}", + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" } ] }, { - "$refId": "Person{0000-0002}", - "age": 35, - "name": "Tony Doe", - "friends": [ + "$refId": "Playlist#{0000-0002}", + "title": "Playlist 3", + "songs": [ + { + "$refId": "Song#{0000-0000}", + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#{0000-0007}", + "artist": "Unique artist 5", + "title": "Unique title 5" + }, + { + "$refId": "Song#{0000-0008}", + "artist": "Unique artist 6", + "title": "Unique title 6" + }, + { + "$refId": "Song#{0000-0001}", + "artist": "Shared artist name 2", + "title": "Shared title name 2" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" + }, { - "$refId": "Person{0000-0001}", - "age": 40, - "name": "Jane Doe", - "friends": [ + "$refId": "Playlist#{0000-0001}", + "title": "Playlist 2", + "songs": [ { - "$ref": "Person{0000-0002}" + "$refId": "Song#{0000-0005}", + "artist": "Unique artist 3", + "title": "Unique title 3" }, { - "$ref": "Person{0000-0000}" + "$refId": "Song#{0000-0006}", + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" } ] } @@ -50,38 +119,94 @@ ] }, { - "$refId": "Person{0000-0001}", - "age": 40, - "name": "Jane Doe", - "friends": [ + "$refId": "Playlist#{0000-0001}", + "title": "Playlist 2", + "songs": [ { - "$refId": "Person{0000-0002}", - "age": 35, - "name": "Tony Doe", - "friends": [ - { - "$ref": "Person{0000-0001}" - } - ] + "$refId": "Song#{0000-0005}", + "artist": "Unique artist 3", + "title": "Unique title 3" }, { - "$refId": "Person{0000-0000}", - "age": 42, - "name": "John Doe", - "friends": [ + "$refId": "Song#{0000-0006}", + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$refId": "Playlist#{0000-0000}", + "title": "Playlist 1", + "songs": [ { - "$ref": "Person{0000-0000}" + "$refId": "Song#{0000-0000}", + "artist": "Shared artist name 1", + "title": "Shared title name 1" }, { - "$ref": "Person{0000-0001}" + "$refId": "Song#{0000-0001}", + "artist": "Shared artist name 2", + "title": "Shared title name 2" }, { - "$refId": "Person{0000-0002}", - "age": 35, - "name": "Tony Doe", - "friends": [ + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#{0000-0003}", + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#{0000-0004}", + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" + }, + { + "$ref": "Playlist#{0000-0001}" + }, + { + "$refId": "Playlist#{0000-0002}", + "title": "Playlist 3", + "songs": [ + { + "$refId": "Song#{0000-0000}", + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#{0000-0007}", + "artist": "Unique artist 5", + "title": "Unique title 5" + }, + { + "$refId": "Song#{0000-0008}", + "artist": "Unique artist 6", + "title": "Unique title 6" + }, { - "$ref": "Person{0000-0001}" + "$refId": "Song#{0000-0001}", + "artist": "Shared artist name 2", + "title": "Shared title name 2" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" + }, + { + "$ref": "Playlist#{0000-0001}" } ] } @@ -90,31 +215,156 @@ ] }, { - "$refId": "Person{0000-0002}", - "age": 35, - "name": "Tony Doe", - "friends": [ + "$refId": "Playlist#{0000-0002}", + "title": "Playlist 3", + "songs": [ + { + "$refId": "Song#{0000-0000}", + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#{0000-0007}", + "artist": "Unique artist 5", + "title": "Unique title 5" + }, + { + "$refId": "Song#{0000-0008}", + "artist": "Unique artist 6", + "title": "Unique title 6" + }, + { + "$refId": "Song#{0000-0001}", + "artist": "Shared artist name 2", + "title": "Shared title name 2" + } + ], + "related": [ + { + "$refId": "Playlist#{0000-0000}", + "title": "Playlist 1", + "songs": [ + { + "$refId": "Song#{0000-0000}", + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#{0000-0001}", + "artist": "Shared artist name 2", + "title": "Shared title name 2" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#{0000-0003}", + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#{0000-0004}", + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" + }, + { + "$refId": "Playlist#{0000-0001}", + "title": "Playlist 2", + "songs": [ + { + "$refId": "Song#{0000-0005}", + "artist": "Unique artist 3", + "title": "Unique title 3" + }, + { + "$refId": "Song#{0000-0006}", + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$ref": "Playlist#{0000-0000}" + } + ] + }, + { + "$ref": "Playlist#{0000-0002}" + } + ] + }, { - "$refId": "Person{0000-0001}", - "age": 40, - "name": "Jane Doe", - "friends": [ + "$refId": "Playlist#{0000-0001}", + "title": "Playlist 2", + "songs": [ { - "$ref": "Person{0000-0002}" + "$refId": "Song#{0000-0005}", + "artist": "Unique artist 3", + "title": "Unique title 3" }, { - "$refId": "Person{0000-0000}", - "age": 42, - "name": "John Doe", - "friends": [ + "$refId": "Song#{0000-0006}", + "artist": "Unique artist 4", + "title": "Unique title 4" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + } + ], + "related": [ + { + "$refId": "Playlist#{0000-0000}", + "title": "Playlist 1", + "songs": [ + { + "$refId": "Song#{0000-0000}", + "artist": "Shared artist name 1", + "title": "Shared title name 1" + }, + { + "$refId": "Song#{0000-0001}", + "artist": "Shared artist name 2", + "title": "Shared title name 2" + }, + { + "$refId": "Song#{0000-0002}", + "artist": "Shared artist name 3", + "title": "Shared title name 3" + }, + { + "$refId": "Song#{0000-0003}", + "artist": "Unique artist 1", + "title": "Unique title 1" + }, + { + "$refId": "Song#{0000-0004}", + "artist": "Unique artist 2", + "title": "Unique title 2" + } + ], + "related": [ { - "$ref": "Person{0000-0000}" + "$ref": "Playlist#{0000-0000}" }, { - "$ref": "Person{0000-0001}" + "$ref": "Playlist#{0000-0001}" }, { - "$ref": "Person{0000-0002}" + "$ref": "Playlist#{0000-0002}" } ] } @@ -122,4 +372,4 @@ } ] } -] +] \ No newline at end of file diff --git a/lib/extensions.js b/lib/extensions.js index 536ec7dc03..2f2fd8aae3 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -91,23 +91,16 @@ module.exports = function(realmConstructor, context) { enumerable: false }); - const serializedPrimaryKeyValue = (value) => { - if (value.toJSON) { - return value.toJSON(); - } - if (value.toString) { - return value.toString(); - } - return value; + const getInternalCacheId = (realmObj) => { + const { name, primaryKey } = realmObj.objectSchema(); + const id = primaryKey ? realmObj[primaryKey] : realmObj._objectId(); + return `${name}#${id}`; } Object.defineProperty(realmConstructor.Object.prototype, "toJSON", { value: function (_, cache = new Map()) { // Construct a reference-id of table-name & primaryKey if it exists, or fall back to objectId. - const { name: schemaName, primaryKey } = this.objectSchema(); - const id = primaryKey - ? serializedPrimaryKeyValue(this[primaryKey]) - : schemaName + this._objectId(); + const id = getInternalCacheId(this); // Check if current objectId has already processed, to keep object references the same. const existing = cache.get(id); @@ -119,22 +112,30 @@ module.exports = function(realmConstructor, context) { const result = {}; cache.set(id, result); - if (primaryKey) { - // Expose the key holding the primaryKey, as a non-enumerable prop, checkable at serialization. - Object.defineProperty(result, "_primaryKeyProp", { value: primaryKey }); - } else { - // Add the generated reference-id, as a non-enumerable prop '$refId', for later exposure though e.g. Realm.JsonSerializationReplacer. - Object.defineProperty(result, "$refId", { value: id, configurable: true }); - } + // Add the generated reference-id, as a non-enumerable prop '$refId', for later exposure though e.g. Realm.JsonSerializationReplacer. + Object.defineProperty(result, "$refId", { value: id, configurable: true }); // Move all enumerable keys to result, triggering any specific toJSON implementation in the process. Object.keys(this) .concat(Object.keys(Object.getPrototypeOf(this))) .forEach(key => { const value = this[key]; - result[key] = value instanceof realmConstructor.Object || value instanceof realmConstructor.Collection - ? value.toJSON(key, cache) - : value; + + // skip any functions & constructors (in case of class models). + if (typeof value === "function") { + return; // continue + } + + if (value instanceof realmConstructor.Object || value instanceof realmConstructor.Collection) { + // recursively trigger `toJSON` for Realm instances with the same cache. + result[key] = value.toJSON(key, cache); + } else if (value instanceof ArrayBuffer) { + // convert "data" to base64 strings. + result[key] = Buffer.from(value).toString("base64"); + } else { + // default to original value. + result[key] = value; + } }); return result; @@ -568,11 +569,8 @@ module.exports = function(realmConstructor, context) { if (seen.includes(value)) { // If we have seen the current value before, return a reference-structure if possible. - if (value._primaryKeyProp) { - return { $ref: value[value._primaryKeyProp] }; - } if (value.$refId) { - return { $ref: value.$refId }; + return { $ref: value.$refId }; } return "[Circular reference]"; }