Skip to content

Commit

Permalink
Added option recurseEverything to toJS
Browse files Browse the repository at this point in the history
  • Loading branch information
wangyiz4262 committed Sep 21, 2018
1 parent 125e5b7 commit 56e2ac2
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 26 deletions.
41 changes: 21 additions & 20 deletions src/api/tojs.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import {
isObservable,
isObservableArray,
isObservableMap,
isObservableObject,
isObservableValue,
keys
} from "../internal"
import { keys } from "./object-api"
import { isObservable } from "./isobservable"
import { isObservableArray } from "../types/observablearray"
import { isObservableValue } from "../types/observablevalue"
import { isObservableMap } from "../types/observablemap"

export type ToJSOptions = {
detectCycles?: boolean
exportMapsAsObjects?: boolean
recurseEverything?: boolean
}

const defaultOptions: ToJSOptions = {
Expand All @@ -23,8 +21,18 @@ function cache<K, V>(map: Map<any, any>, key: K, value: V, options: ToJSOptions)
}

function toJSHelper(source, options: ToJSOptions, __alreadySeen: Map<any, any>) {
if (typeof source !== "object") {
return source
if (!options.recurseEverything && !isObservable(source)) return source

if (typeof source !== "object") return source

// Directly return the Date object itself if contained in the observable
if (source instanceof Date) return source

if (isObservableValue(source)) return toJSHelper(source.get(), options!, __alreadySeen)

// make sure we track the keys of the object
if (isObservable(source)) {
keys(source)
}

const detectCycles = options.detectCycles === true
Expand All @@ -33,7 +41,7 @@ function toJSHelper(source, options: ToJSOptions, __alreadySeen: Map<any, any>)
return __alreadySeen.get(source)
}

if (isObservableArray(source) || Object.getPrototypeOf(source) === Array.prototype) {
if (isObservableArray(source) || Array.isArray(source)) {
const res = cache(__alreadySeen, source, [] as any, options)
const toAdd = source.map(value => toJSHelper(value, options!, __alreadySeen))
res.length = toAdd.length
Expand All @@ -57,20 +65,13 @@ function toJSHelper(source, options: ToJSOptions, __alreadySeen: Map<any, any>)
}
}

if (isObservableValue(source)) return toJSHelper(source.get(), options!, __alreadySeen)

// Directly return the Date object itself if contained in the observable
if (source instanceof Date) return source

// Fallback to situation if source is an ObservableObject or a plain object
// Fallback to the situation that source is an ObservableObject or a plain object
const res = cache(__alreadySeen, source, {}, options)
Object.setPrototypeOf(res, Object.getPrototypeOf(source))
for (let key in source) {
res[key] = toJSHelper(source[key], options!, __alreadySeen)
}
return res

return source
return res
}

/**
Expand Down
50 changes: 44 additions & 6 deletions test/base/tojs.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ test("json2", function() {
url: "booking.com"
}
})
expect(mobx.toJS(o)).toEqual({
expect(JSON.parse(JSON.stringify(o))).toEqual({
todos: [
{
title: "write blog",
Expand Down Expand Up @@ -288,7 +288,7 @@ test("#285 non-mobx class instances with toJS", () => {
// check before lazy initialization
expect(mobx.toJS(p1)).toEqual({
firstName: "michel",
lastName: "weststrate" // toJS will recurse into any object that may contain observable value
lastName: nameObservable // toJS doesn't recurse into non observable objects!
})
})

Expand All @@ -298,11 +298,9 @@ test("verify #566 solution", () => {
const b = mobx.observable({ x: 3 })
const c = mobx.observable({ a: a, b: b })

expect(mobx.toJS(c).a).toEqual(a)
expect(mobx.toJS(c).a).toBeInstanceOf(MyClass)
expect(mobx.isObservableObject(c.b)).toBeTruthy()
expect(mobx.isObservableObject(mobx.toJS(c).b)).toBeFalsy()
expect(mobx.toJS(c).a).toBe(a)
expect(mobx.toJS(c).b).toEqual(mobx.toJS(c.b))
expect(mobx.toJS(c).b.x).toEqual(b.x)
})

test("verify already seen", () => {
Expand Down Expand Up @@ -343,3 +341,43 @@ test("json cycles when exporting maps as maps", function() {
expect(cloneD.get("c")).toBe(cloneC)
expect(cloneA.e).toBe(cloneA)
})

describe("recurseEverything set to true", function() {
test("prototype chain will be removed even if the object is not observable", function() {
function Person() {
this.firstname = "michel"
this.lastname = "weststrate"
}
const p = new Person()

expect(mobx.toJS(p)).toBeInstanceOf(Person)
expect(mobx.toJS(p, { recurseEverything: true })).not.toBeInstanceOf(Person)
expect(mobx.toJS(p)).toEqual({ firstname: "michel", lastname: "weststrate" })
expect(mobx.toJS(p)).toEqual(mobx.toJS(p, { recurseEverything: true }))
})

test("properties on prototype should be flattened to plain object", function() {
const observableValue = mobx.observable.box("b")
const Base = function() {
this.a = "a"
}
const derived = Object.create(new Base(), {
b: { value: observableValue, enumerable: true }
})

const simpleCopy = mobx.toJS(derived)
const deepCopy = mobx.toJS(derived, { recurseEverything: true })
expect(simpleCopy).toBeInstanceOf(Base)
expect(simpleCopy).toEqual({ b: observableValue })
expect(simpleCopy.hasOwnProperty("a")).toBeFalsy()

expect(deepCopy).not.toBeInstanceOf(Base)
expect(deepCopy).toEqual({ a: "a", b: "b" })
expect(deepCopy.hasOwnProperty("a")).toBeTruthy()
})

test("Date type should not be converted", function() {
const date = new Date()
expect(mobx.toJS(mobx.observable.box(date), { recurseEverything: true })).toBe(date)
})
})

0 comments on commit 56e2ac2

Please sign in to comment.