32

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!

Sigfried
  • 2,943
  • 3
  • 31
  • 43
  • 1
    Why is the library choking on your proxy? Please post (the relevant part of) the code of the library, and of your proxy, so that we can help you fix your handlers. – Bergi Nov 01 '18 at 14:48

9 Answers9

37

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))
Alex Park
  • 651
  • 8
  • 15
  • 2
    smart :) sometimes things can be so simple, thank you! – Basti Jul 16 '19 at 15:28
  • 1
    Doesn't this end up losing some stuff? People seem to like it. – Sigfried Mar 13 '20 at 11:51
  • 2
    @Sigfried well, as was said in the accepted answer it is losing some stuff, but I guess the most common case when you don't have these kinds of properties in your object. I think it's only good as a debugging tool at least I was using it only with this purpose. JSON.stringify({ key: undefined }); JSON.stringify({ key: Symbol() }); JSON.stringify({ key: function(){} }); // all will be converted to just "{}" – Alex Park Mar 16 '20 at 08:26
  • This isn not smart at all – you Dates will be messed up after parse and stringify – eXception Oct 14 '22 at 08:55
  • It's much slower than deep clone. – Marek Marczak Oct 18 '22 at 05:51
  • Don't do this, cause your dates will be messed up after this transform – eXception Jan 12 '23 at 10:20
23

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);
}
ghiscoding
  • 12,308
  • 6
  • 69
  • 112
  • This appeals to me, since I generally trust lodash, and it hides all the confusing stuff. I'm no longer using whatever that library was that was messing me up, so I can't test the different answers. If anyone objects to my choosing this one as the official answer, let me know and I'll revisit. – Sigfried Feb 24 '20 at 14:57
12

How about using the Spread Operator?

 const plainObject = { ...proxyObject };
Cycododge
  • 943
  • 1
  • 8
  • 10
  • 1
    Upvoted because it works for my situation but this only seems to remove the proxy on the 'root' of the object. – a2f0 Oct 18 '21 at 01:44
  • upvoted. this is the simplest way without using any library. – shtse8 Jul 16 '22 at 21:03
  • 3
    Doesn't work for nested `Proxy` (it's just a shallow copy), to copy/clone **entire** object recursively use [`_.cloneDeep()`](https://lodash.com/docs/4.17.15#cloneDeep) – nezort11 Sep 23 '22 at 10:02
5
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(...))

Sigfried
  • 2,943
  • 3
  • 31
  • 43
  • 2
    This results in `{a: undefined}` rather than `{a: 1}`, if the `get` handler is still there. Otherwise, [`({...proxyObj})`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) is even shorter. – Sebastian Simon Nov 01 '18 at 14:01
  • 2
    Your `get` handler needs an `else` case where it behaves normally (i.e. `Reflect.get`) on non-`target` properties. – Bergi Nov 01 '18 at 14:50
  • If one of you would like to add an answer, I'd be happy to delete my botched one. – Sigfried Nov 02 '18 at 00:44
4

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

Javaddict
  • 483
  • 1
  • 4
  • 15
  • 2
    Thank you for contributing an answer. Would you kindly edit your answer to to include an explanation of your code? That will help future readers better understand what is going on, and especially those members of the community who are new to the language and struggling to understand the concepts. That's especially important when there's already an accepted answer that's been validated by the community. Under what conditions might your approach be preferred? Are you taking advantage of new capabilities? – Jeremy Caney Nov 04 '21 at 00:43
  • 1
    toRaw function does not return POJO object. It;s misunderstanding and a mistake in the docs. As you can see here It disables reacrivity using a flag and that's all. This misunderstanding is a source of bugs while working with Web Workers and Vue docs should be fixed ASAP. https://github.com/vuejs/vue/blob/main/src/v3/reactivity/reactive.ts – Marek Marczak Oct 17 '22 at 05:28
2

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);
David Gilbertson
  • 4,219
  • 1
  • 26
  • 32
  • Thanks. I edited mine to refer people to this then. It would be nice if someone summarized and compared the pro/cons of the different answers. Maybe I will someday if no one else does. – Sigfried Mar 13 '20 at 11:59
2

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

import { observable, toJS } from "mobx";

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

const obj = toJS(observed)
2

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));
Neil Rackett
  • 159
  • 1
  • 6
  • Please explain your solution. Answers which do not have an explanation and are only code get flagged as low effort. – cursorrux Sep 09 '21 at 12:57
  • When I tried your two suggestions, it just creates a new object with the proxy inside of it, which doesn't solve the problem. – McGrew Oct 31 '21 at 00:48
  • Both examples were tested before being posted, so I've added an example to show the new objects output from each. – Neil Rackett Nov 01 '21 at 08:56
0

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

Bikibird
  • 125
  • 1
  • 5