realm-js
realm-js copied to clipboard
Spreading a Realm.Object gives in an empty object
Goals
I would like to be able to easily convert a Realm.Object
to a plain-ol'-javascript-object.
Expected Results
When doing a JS spread on a Realm.Object
I would expect the resulting object contained the same properties as the Realm.Object
.
Actual Results
You get an empty object.
Steps to Reproduce & Code Sample
const Realm = require('realm')
const realm = new Realm({ schema: [ { name: "Person", properties: { name: "string" } } ] });
realm.write(() => { realm.create("Person", { name: "Alice" }) });
const persons = realm.objects("Person"); // 👉Results { [0]: RealmObject { [name]: 'Alice' } }
const alice = persons[0]; // 👉RealmObject { [name]: 'Alice' }
Object.keys(alice) // 👉[ 'name' ]
Object.getOwnPropertyNames(alice) // 👉[ '_realm', 'name' ]
alice.hasOwnProperty('name') // 👉true
Object.getOwnPropertyDescriptors(alice)
/* 👇
{ _realm:
{ value: Realm {},
writable: true,
enumerable: false,
configurable: false },
name:
{ value: 'Alice',
writable: true,
enumerable: false,
configurable: true } }
*/
{ ...alice } // 👉{}
An interesting observation is that getOwnPropertyNames
contains 'name'
but Object.hasOwnProperty(alice, 'name')
is false 🤷♂️
Version of Realm and Tooling
- Realm JS SDK Version: 3.5.0
- Node or React Native: Node v10.17.0
- Client OS & Version: N/A
- Which debugger for React Native: None
Spread operator is very tricky and makes confusing about behaviour. It doesn't copy getters/setters, just values: https://2ality.com/2016/10/rest-spread-properties.html#both-spread-and-object.assign()-read-values-via-a-“get”-operation and implentation in RealmObject class https://github.com/realm/realm-js/blob/cd579770ad50bba5ce6dcfa89591334fd1735c53/lib/browser/objects.js#L59
Anyway. hasOwnProperty
is not class method, it is object method. You need to call this like this alice.hasOwnProperty('name')
or Object.prototype.hasOwnProperty.call(alice, 'name')
@radeno thanks for your interest.
To me the spread operator is pretty straight forward - it makes sense to me that it doesn't copy getters/setters, but this is also not what this issue is about. Thanks for the hint on the use of hasOwnProperty
- but honestly, that was just there to show what the value might be: I've updated the code.
I believe the root cause of this issue is that both spread and Object.assign() only consider own enumerable properties:
Object.getOwnPropertyDescriptors(alice)
/* 👇
{ _realm:
{ value: Realm {},
writable: true,
enumerable: false,
configurable: false },
name:
{ value: 'Alice',
writable: true,
enumerable: false,
configurable: true } }
*/
Same issue here, tested on RN "realm": "ˆ5.0.3" "react": "^16.13.1" "react-native": "0.62.2"
I'trying to load an Object to a const, that I'm using with the useState hook, that's because i can change the full object fields with just one useState setter callback const testobj = realm.objectForPrimaryKey(... Then if i spread testobj the return value is empty {} Also I've try to take the first element from objects, same result, spread is empty const testobj = realm.objects(... )[0]
I've try the JSON.parse(JSON.stringify(testobj)) trick, but I've no luck with dates that became strings, and also it's not that beauty. Any other suggestion to stread a Realm.Object or a Realm.Result?
thank you!
For those who are interested in, I've found a quick fix here, I'm testing it (mapProps method) https://github.com/realm/realm-js/issues/141#issuecomment-340663517
Even i'm facing the same issue where hasOwnProperty throw false.
Created an alternative solution in typescript. Haven't tried using shallow clone, but deep clone seems to do the job. Obviously you can implement your own cloneDeep function, but lodash is used here out of pure laziness.
import cloneDeep from 'lodash-es/cloneDeep';
export function deepCloneRealmObject<T>(realmObject: Realm.Object): T {
const clone: any = {};
realmObject.keys().forEach((key) => {
if (typeof realmObject[key] == 'object') {
clone[key] = cloneDeep(realmObject[key]);
} else {
clone[key] = realmObject[key];
}
});
return clone;
}
This works for me.
let data = realm.objectForPrimaryKey('User', 10);
data = JSON.parse(JSON.stringify(data));
console.log({...data, name: 'Bob'});
We found lodash functions such as orderBy and isEmpty not working with realmObject anymore which worked well with the legacy realm version.
import Realm from 'realm'
export function shallowCloneRealmObject<T extends Realm.Object>(x: T) {
const copy: any = {}
for (const key in x) {
copy[key] = x[key]
}
return copy as Exclude<T, Realm.Object>
}
My workaround for now:
- It's a shallow clone so very close to spread
- The cloned object won't be a live realm object which is what you would expect when spreading. This is also reflected by the types.
If people are interested I published to npm an eslint-plugin that bans spread on Realm.Object type. https://www.npmjs.com/package/@waltari/eslint-plugin-ban-realm-spread
(hopefully spread operator wont be fixed now the next day)
Note that JSON.parse(JSON.stringify(data))
will convert all ObjectIds to strings.
Version 12.0.0 introduced support for the spread operator