From 41760956dc47064d07f8367218ad08a7a9c5a422 Mon Sep 17 00:00:00 2001 From: Tom Duncalf Date: Fri, 24 Jun 2022 12:48:04 +0100 Subject: [PATCH] Fix toJSON behaviour for Dictionary on JSC pre-v11 (fixes #4658) --- .../tests/src/tests/dictionary.ts | 24 +++++++++++++++++++ lib/dictionary.js | 7 ++++++ lib/extensions.js | 8 ++++++- package.json | 2 +- packages/realm-common/package.json | 2 +- packages/realm-common/src/symbols.ts | 6 +++++ packages/realm-network-transport/package.json | 2 +- packages/realm-react/package.json | 2 +- packages/realm-web/package.json | 2 +- 9 files changed, 49 insertions(+), 6 deletions(-) diff --git a/integration-tests/tests/src/tests/dictionary.ts b/integration-tests/tests/src/tests/dictionary.ts index 1e96f10a58a..44e9007383a 100644 --- a/integration-tests/tests/src/tests/dictionary.ts +++ b/integration-tests/tests/src/tests/dictionary.ts @@ -206,6 +206,30 @@ describe("Dictionary", () => { }); }); + describe("toJSON", function () { + openRealmBefore({ + schema: [ + { + name: "Item", + properties: { dict: "{}" }, + }, + ], + }); + + it("calling toJSON on an object with a Dictionary field removes the Proxy from the Dictionary", function (this: RealmContext) { + const object = this.realm.write(() => { + return this.realm.create("Item", { dict: { something: "test" } }); + }); + const jsonObject = object.toJSON(); + + // Previously this would throw on JSC, because the Dictionary was still a Proxy, + // so modifying it tried to write to the Realm outside of a write transaction + expect(() => { + jsonObject.dict.something = "test2"; + }).to.not.throw(); + }); + }); + type ValueGenerator = (realm: Realm) => DictValues; type TypedDictionarySuite = { diff --git a/lib/dictionary.js b/lib/dictionary.js index 16d0ca62a4c..10aa100c3fc 100644 --- a/lib/dictionary.js +++ b/lib/dictionary.js @@ -16,8 +16,15 @@ // //////////////////////////////////////////////////////////////////////////// +const { symbols } = require("@realm.io/common"); + const dictionaryHandler = { get(target, key) { + // Allows us to detect if this is a proxied Dictionary on JSC pre-v11. See realm-common/symbols.ts for details. + if (key === symbols.IS_PROXIED_DICTIONARY) { + return true; + } + if (key === "toJSON") { return function () { const keys = target._keys(); diff --git a/lib/extensions.js b/lib/extensions.js index edb338bf5bb..3332686145e 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -16,6 +16,8 @@ // //////////////////////////////////////////////////////////////////////////// +const { symbols } = require("@realm.io/common"); + let getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function (obj) { @@ -109,7 +111,11 @@ module.exports = function (realmConstructor) { // recursively trigger `toJSON` for Realm instances with the same cache. if (value instanceof realmConstructor.Object || value instanceof realmConstructor.Collection) { result[key] = value.toJSON(key, cache); - } else if (value instanceof realmConstructor.Dictionary) { + } else if ( + value instanceof realmConstructor.Dictionary || + // Allows us to detect if this is a proxied Dictionary on JSC pre-v11. See realm-common/symbols.ts for details. + value[symbols.IS_PROXIED_DICTIONARY] + ) { // Dictionary special case to share the "cache" for dictionary-values, // in case of circular structures involving links. result[key] = Object.fromEntries( diff --git a/package.json b/package.json index aa8795465f9..bf611c8e93c 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "postinstall": "node scripts/submit-analytics.js" }, "dependencies": { - "@realm.io/common": "^0.1.3", + "@realm.io/common": "^0.1.4", "bindings": "^1.5.0", "bson": "4.4.1", "clang-format": "^1.6.0", diff --git a/packages/realm-common/package.json b/packages/realm-common/package.json index 04211125903..83b8ddd2d86 100644 --- a/packages/realm-common/package.json +++ b/packages/realm-common/package.json @@ -1,6 +1,6 @@ { "name": "@realm.io/common", - "version": "0.1.3", + "version": "0.1.4", "description": "Cross-product common code used by Realm", "main": "./dist/bundle.cjs.js", "module": "./dist/bundle.es.js", diff --git a/packages/realm-common/src/symbols.ts b/packages/realm-common/src/symbols.ts index 204a5f13ff2..689dcdc5ea5 100644 --- a/packages/realm-common/src/symbols.ts +++ b/packages/realm-common/src/symbols.ts @@ -19,3 +19,9 @@ // Used as a key by Realm React in `useQuery`, to store the original object // which is being proxied, for compatibility with JSC pre-v11 (#4541) export const PROXY_TARGET = Symbol("PROXY_TARGET"); + +// Used to indicate that an object is a proxied Realm.Dictionary, to allow us +// to correctly detect Dictionaries in toJSON when using JSC pre-v11 (#4674) +export const IS_PROXIED_DICTIONARY = Symbol("IS_PROXIED_DICTIONARY"); + +// todo add test for dict to json in jsc? diff --git a/packages/realm-network-transport/package.json b/packages/realm-network-transport/package.json index 715b08d1764..39dccd1b71c 100644 --- a/packages/realm-network-transport/package.json +++ b/packages/realm-network-transport/package.json @@ -35,7 +35,7 @@ }, "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@realm.io/common": "^0.1.3", + "@realm.io/common": "^0.1.4", "abort-controller": "^3.0.0", "node-fetch": "^2.6.0" }, diff --git a/packages/realm-react/package.json b/packages/realm-react/package.json index 637da5bcd8e..10228b6e6e0 100644 --- a/packages/realm-react/package.json +++ b/packages/realm-react/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "lodash": "^4.17.21", - "@realm.io/common": "^0.1.2" + "@realm.io/common": "^0.1.4" }, "devDependencies": { "@babel/core": "^7.15.5", diff --git a/packages/realm-web/package.json b/packages/realm-web/package.json index 7f1599f3fe0..ed4702b1f24 100644 --- a/packages/realm-web/package.json +++ b/packages/realm-web/package.json @@ -46,7 +46,7 @@ }, "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@realm.io/common": "^0.1.3", + "@realm.io/common": "^0.1.4", "bson": "^4.5.4", "detect-browser": "^5.2.1", "js-base64": "^3.7.2"