Skip to content

Commit

Permalink
Add toJSON implementation and testing (#4966)
Browse files Browse the repository at this point in the history
  • Loading branch information
gagik authored and RedBeard0531 committed Nov 23, 2022
1 parent 60cf7ee commit a402951
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 171 deletions.

This file was deleted.

64 changes: 0 additions & 64 deletions integration-tests/tests/src/schemas/playlist-with-songs.ts

This file was deleted.

10 changes: 6 additions & 4 deletions integration-tests/tests/src/tests/serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import { expect } from "chai";
import Realm from "realm";

type DefaultObject = Record<string, unknown>;

import { openRealmBefore } from "../hooks";

const PlaylistSchema: Realm.ObjectSchema = {
Expand Down Expand Up @@ -77,7 +79,7 @@ interface TestSetup {
// Type of the Realm instance
type: typeof Realm.Object | typeof Realm.Results | typeof Realm.Dictionary;
// Expected serialized plain object result
serialized: Record<string, any>;
serialized: DefaultObject;
}

// Describe common test types that will be run,
Expand All @@ -89,9 +91,9 @@ describe("toJSON functionality", () => {
commonTests: Record<string, TestSetup>;
playlists: Realm.Results<Realm.Object> & IPlaylist[];
birthdays: Realm.Object & IBirthdays;
p1Serialized: Record<string, any>;
resultsSerialized: Record<string, any>;
birthdaysSerialized: Record<string, any>;
p1Serialized: DefaultObject;
resultsSerialized: DefaultObject;
birthdaysSerialized: DefaultObject;
} & RealmContext;
openRealmBefore({
inMemory: true,
Expand Down
13 changes: 13 additions & 0 deletions packages/realm/src/Dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import * as binding from "./binding";
import { Collection } from "./Collection";
import { IllegalConstructorError } from "./errors";
import { INTERNAL } from "./internal";
import { DefaultObject } from "./schema";
import { Object as RealmObject } from "./Object";
import { TypeHelpers } from "./types";
import { JSONCacheMap } from "./JSONCacheMap";

const HELPERS = Symbol("Dictionary#helpers");

Expand Down Expand Up @@ -217,4 +220,14 @@ export class Dictionary<T = unknown> extends Collection<string, T, [string, T],
throw new Error(`Failed to remove missing key${keySuffix} from dictionary: ${keysSummary}`);
}
}

/**
* @returns A plain object for JSON serialization.
**/
// @ts-expect-error We're exposing methods in the users value namespace
toJSON(_?: string, cache = new JSONCacheMap()): DefaultObject {
return Object.fromEntries(
Object.entries(this).map(([k, v]) => [k, v instanceof RealmObject ? v.toJSON(k, cache) : v]),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@
//
////////////////////////////////////////////////////////////////////////////

import { Realm } from "../index";
import { INTERNAL } from "./internal";
import { DefaultObject } from "./schema";
import { Object as RealmObject } from "./Object";

import { closeRealm, generateTempRealmPath, RealmContext } from "./utils";

describe("Serializing", () => {
describe("an Object", () => {
after(closeRealm);
it("returns a plain object", function (this: RealmContext) {
this.realm = new Realm({
path: generateTempRealmPath(),
inMemory: true,
schema: [{ name: "Person", properties: { name: "string", age: "int", bestFriend: "Person" } }],
});
const alice = this.realm.write(() => this.realm.create("Person", { name: "Alice", age: 32 }));
const serialized = alice.toJSON();
console.log({ serialized });
});
});
});
////////////////////////////////////////////////////////////////////////////
export class JSONCacheMap<T = unknown> extends Map<number, Map<string, DefaultObject>> {
add(object: RealmObject<T>, value: DefaultObject) {
const tableKey = object[INTERNAL].table.key;
let cachedMap = this.get(tableKey);
if (!cachedMap) {
cachedMap = new Map();
this.set(tableKey, cachedMap);
}
cachedMap.set(object._objectKey(), value);
}
find(object: RealmObject<T>) {
return this.get(object[INTERNAL].table.key)?.get(object._objectKey());
}
}
34 changes: 30 additions & 4 deletions packages/realm/src/Object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import * as binding from "./binding";
import { INTERNAL } from "./internal";
import { Realm } from "./Realm";
import { Results } from "./Results";
import { OrderedCollection } from "./OrderedCollection";
import { CanonicalObjectSchema, Constructor, DefaultObject, RealmObjectConstructor } from "./schema";
import { ObjectChangeCallback, ObjectListeners } from "./ObjectListeners";
import { INTERNAL_HELPERS, ClassHelpers } from "./ClassHelpers";
import { RealmInsertionModel } from "./InsertionModel";
import { assert } from "./assert";
import { TypeAssertionError } from "./errors";
import { JSONCacheMap } from "./JSONCacheMap";
import { Dictionary } from "./Dictionary";

export enum UpdateMode {
Never = "never",
Expand Down Expand Up @@ -214,14 +217,37 @@ class RealmObject<T = DefaultObject> {
entries(): [string, unknown][] {
throw new Error("Not yet implemented");
}
toJSON(): unknown {
// return { ...this };

/**
* @returns A plain object for JSON serialization.
**/
toJSON(_?: string, cache = new JSONCacheMap<T>()): DefaultObject {
// Construct a reference-id of table-name & primaryKey if it exists, or fall back to objectId.

// Check if current objectId has already processed, to keep object references the same.
const existing = cache.find(this);
if (existing) {
return existing;
}
const result: DefaultObject = {};
cache.add(this, result);
// Move all enumerable keys to result, triggering any specific toJSON implementation in the process.
for (const key in this) {
const value = this[key];
console.log({ key, value });
if (typeof value == "function") {
continue;
}
if (value instanceof RealmObject || value instanceof OrderedCollection || value instanceof Dictionary) {
// recursively trigger `toJSON` for Realm instances with the same cache.
result[key] = value.toJSON(key, cache);
} else {
// Other cases, including null and undefined.
result[key] = value;
}
}
return { ...this };
return result;
}

isValid(): boolean {
return this[INTERNAL] && this[INTERNAL].isValid;
}
Expand Down
25 changes: 16 additions & 9 deletions packages/realm/src/OrderedCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ import { Results } from "./Results";
import { Collection } from "./Collection";
import { unwind } from "./ranges";
import { TypeHelpers } from "./types";
import { getBaseTypeName } from "./schema";
import { IllegalConstructorError, TypeAssertionError } from "./errors";
import { Realm } from "./Realm";
import { Object as RealmObject } from "./Object";
import { getInternal } from "./internal";
import { assert } from "./assert";
import { ClassHelpers } from "./ClassHelpers";
import { JSONCacheMap } from "./JSONCacheMap";
import { Object as RealmObject } from "./Object";
import { DefaultObject, getBaseTypeName } from "./schema";

const DEFAULT_COLUMN_KEY = 0n as unknown as binding.ColKey;

Expand Down Expand Up @@ -177,6 +178,19 @@ export abstract class OrderedCollection<T = unknown>
throw new Error(`Assigning into a ${this.constructor.name} is not support`);
}

/**
* @returns An array of plain objects for JSON serialization.
**/
toJSON(_?: string, cache = new JSONCacheMap()): Array<DefaultObject> {
return this.map((item, index) => {
if (item instanceof RealmObject) {
return item.toJSON(index.toString(), cache);
} else {
return item as DefaultObject;
}
});
}

*keys() {
const size = this.results.size();
for (let i = 0; i < size; i++) {
Expand Down Expand Up @@ -324,13 +338,6 @@ export abstract class OrderedCollection<T = unknown>

// Other methods

/**
* @returns An object for JSON serialization.
*/
toJSON(): Array<unknown> {
throw new Error("Method not implemented.");
}

description(): string {
throw new Error("Method not implemented.");
}
Expand Down

0 comments on commit a402951

Please sign in to comment.