how do I turn an ES6 Proxy back into a plain object (POJO)?

Viewed 21807

I'm using a library that turns things into ES6 Proxy objects, and another library that I think is choking because I'm passing it one of these (my code is a travesty, I know), and I couldn't figure out how to unProxy the Proxy object.

But I was just being dumb. Proxies can do anything!

9 Answers

I find a hack. In my case, I can't control the creation of the proxy(mobx observable values). So the solution is:

JSON.parse(JSON.stringify(your.object))

Tried using JSON.parse(JSON.stringify(proxyObj)) but that removes anything that cannot be stringified (like classes, functions, callback, etc...) which isn't good for my use case since I have some callback function declarations in my object and I want to see them as part of my object. However I found out that using the Lodash cloneDeep function does a real good job at converting a Proxy object into a POJO while keeping the object structure.

convertProxyObjectToPojo(proxyObj) {
  return _.cloneDeep(proxyObj);
}

How about using the Spread Operator?

 const plainObject = { ...proxyObject };
pp = new Proxy(
   {a:1},
   {
      get: function(target, prop, receiver) { 
             if(prop==='target') return target 
           }
   }
)

But that will only work if you can control creation of the proxy. It turns out to be even easier though:

pojo = Object.assign({}, proxyObj) // won't unwrap nested proxies though

For readers who might like this answer, David Gilbertson's newer answer might be better. I personally prefer lodash clonedeep. And the most popular seems to be JSON.parse(JSON.stringify(...))

If you don't want to use lodash The object.assign({},val) method is OK. But if the val contains nested objects they will be proxified. So it has to be done recursively like that:

function unproxify(val) {
    if (val instanceof Array) return val.map(unproxify)
    if (val instanceof Object) return Object.fromEntries(Object.entries(Object.assign({},val)).map(([k,v])=>[k,unproxify(v)]))
    return val
}

In fact it is a deep clone function. I think that it does not work if val contains Map objects but you can modify the function for that following the same logic.

If you want to unproxify a Vue3 proxy, Vue3 provides a function : toRaw

Thank you @sigfried for the answer, that's exactly what I was looking for!

Here's a slightly more detailed version, using Symbol to avoid clashes with real prop names.

const original = {foo: 'bar'};

const handler = {
  get(target, prop) {
    if (prop === Symbol.for('ORIGINAL')) return target;

    return Reflect.get(target, prop);
  }
};

const proxied = new Proxy(original, handler);

console.assert(original !== proxied);

const unproxied = proxied[Symbol.for('ORIGINAL')];

console.assert(original === unproxied);

Normally you'd use mobx util toJS() for this.

import { observable, toJS } from "mobx";

const observed = observable({ foo: 'bar' })

const obj = toJS(observed)

Assuming you don't have access to the original target, the quickest way to convert a Proxy is to assign its values to a new plain Object:

const obj = Object.assign({}, proxy);

Or use a spreader:

const obj = { ...proxy };

Example

const user = {
  firstName: 'John',
  lastName: 'Doe',
  email: 'john.doe@example.com',
};

const handler = {
  get(target, property) {
    return target[property];
  }
};

const proxy = new Proxy(user, handler);

const a = Object.assign({}, proxy);
console.log(`Using Object.assign():`);
console.log(`Hello, ${a.firstName} ${a.lastName}!`);
console.log(JSON.stringify(a));

const s = { ...proxy };
console.log(`Using spreader:`);
console.log(`Hello, ${s.firstName} ${s.lastName}!`);
console.log(JSON.stringify(s));

In the unproxied object constructor, add this.self=this

Then make sure your get handler allows the self property to be returned and you are all set. proxiedMyObj.self===myObj //returns true

Related