diff --git a/src/common/utils/__tests__/toJS.test.ts b/src/common/utils/__tests__/toJS.test.ts new file mode 100644 index 0000000000..70ea6a2735 --- /dev/null +++ b/src/common/utils/__tests__/toJS.test.ts @@ -0,0 +1,25 @@ +import { isObservable, observable } from "mobx"; +import { toJS } from "../toJS"; + +describe("utils/toJS(data: any)", () => { + const y = { y: 2 }; + + const data = observable({ x: 1, y }, {}, { + deep: false, // this will keep ref to "y" + }); + const data2 = { + x: 1, // partially observable + y: observable(y), + }; + + test("converts mobx-observable to corresponding js struct with links preserving", () => { + expect(toJS(data).y).toBe(y); + expect(isObservable(toJS(data).y)).toBeFalsy(); + }); + + test("converts partially observable js struct", () => { + expect(toJS(data2).y).not.toBe(y); + expect(toJS(data2).y).toEqual(y); + expect(isObservable(toJS(data2).y)).toBeFalsy(); + }); +}); diff --git a/src/common/utils/toJS.ts b/src/common/utils/toJS.ts new file mode 100644 index 0000000000..35965c4547 --- /dev/null +++ b/src/common/utils/toJS.ts @@ -0,0 +1,25 @@ +// Converts mobx-observable or partially observable object to corresponding plain JS-structure. +// Since mobx >= 6.x toJS() recursively converts only top-level observables. +import * as mobx from "mobx"; + +export function toJS(data: T): T { + if (mobx.isObservable(data)) { + return mobx.toJS(data); // data recursively converted, nothing to worry about. + } + + // convert top-level plain array or object + if (typeof data === "object" && data !== null) { + let convertedData: any[] | T; + + if (Array.isArray(data)) { + convertedData = data.map(toJS); + } else { + convertedData = Object.fromEntries( + Object.entries(data).map(([key, value]) => [key, toJS(value)]) + ) as T; + } + Object.assign(data, convertedData); + } + + return data; +}