Stringify object differently depending on context - javascript

I have an object with a toJSON method:
const v = {
foo: 'zoom1',
bar: 'zoom2',
toJSON(){
return {foo: this.foo}
}
}
when I call JSON.stringify(v) I will get {foo:'zoom1'} - my question is - is there a way to stringify it differently depending on who is calling JSON.stringify(), for example, what if I want:
{bar: 'zoom2'}
instead? Maybe there is a good design pattern for this, dunno.

Actually, you could use the replacer parameter of the stringify function:
A function that alters the behavior of the stringification process, or an array of String and Number objects that serve as a whitelist for selecting/filtering the properties of the value object to be included in the JSON string. If this value is null or not provided, all properties of the object are included in the resulting JSON string.
Example:
const v = {
foo: 'zoom1',
bar: 'zoom2'
};
JSON.stringify(v, ['foo']);
// Outputs "{"foo":"zoom1"}
JSON.stringify(v, ['bar']);
// Outputs "{"bar":"zoom2"}
JSON.stringify(v, ['foo', 'bar']);
// Outputs "{"foo":"zoom1", "bar":"zoom2"}

There are a couple deprecated methods that allow you to determine who called a particular function: arguments.callee and function.caller.
However, I would suggest that instead of altering the object you are trying to serialize, or monkey patching a built-in serialization function, you instead just define helper serialization methods that will return the object in different ways (like different views on the same data)
Something like the following idea, if flat and deep are the two different views you want to return of the object:
const myData = {}
const flatSerializer = (data) => {}
const deepSerializer = (data) => {}
const flatString = flatSerializer(myData)
const deepString = deepSerializer(myData)
If you want to introduce some function composition, you could also create different ways of representing the data that are passed into the same serialization function.
Using that same example, you might have something like:
const myData = {}
const serializer = fn => data => fn(data)
const flat = data => {}
const deep = data => {}
const flatSerialize = serializer(flat)
const deepSerialize = serializer(deep)
const flatString = flatSerialize(myData)
const deepString = deepSerialize(myData)

One option could be to use the replacer argument to stringify. It is a function called with the key and value of each property being stringified in the Object, and if you return undefined from it, the property is excluded from stringify result.
You could have a bunch of replacer functions in a library each of which returns undefined for a set of properties you want to exclude i.e. so essentially creating different "views" of the object. Then the caller selects which of the replacers it wants when calling stringify.
Reference

Haven't tried this yet, but I think a custom replacer is the answer, something like this:
const result = JSON.stringify(v, (val) => {
if(val instanceof whatever){
return {bar: val.bar};
}
return val;
});

Related

Is it possible to override Function.prototype.toJSON so that JSON.stringify could work with functions?

Or maybe even override some portion of JSON.parse to parse functions?
This isn't time sensitive, I built work arounds in my code, but with the eval function, you would think that turning functions into strings and back would be a piece of cake.
It's possible, but weird, and you of course wouldn't have access to any of the outer scope of the parsed function. Call toString to get the function's source code, trim out the brackets so you just have the function body, and have Function.prototype.toJSON return that. Then when parsing, call new Function on the string:
Function.prototype.toJSON = function() {
// trim out beginning and end {}s
return this.toString().match(/[^{]*(?=}$)/)[0];
};
const fn = () => {
console.log('foo');
};
const json = JSON.stringify({
str: 'str',
fn
});
console.log(json);
const parsed = JSON.parse(json);
const parsedFn = new Function(parsed.fn);
parsedFn();
But there shouldn't be a need to do this in 99% of situations. Whatever the actual issue is, there's probably a more elegant solution.
I think no need to strip brackets. I use more simple code. It allows you to pass args to your function:
Function.prototype.toJSON = function() { return this.toString(); };
const json = JSON.stringify({
func: (a,b) => { console.log(a,b); },
a: 1
});
const parsed = JSON.parse(json);
const parsedFunc = Function('return ' + parsed.func + ';')();
parsedFunc('hello','world');

JS Proxy & destructuring assignment

As what i know js Proxies make possible to overload classic object getter with a function call.
So that we can do things like that :
const value = myProxy.value;
All that calling in fact the value getter function inside Proxy.
My question is ... is there a way to use the JS destructuring syntax with JS Proxies ?
So that we could do things like that :
const { value } = myProxy;
Based on my tests, the second way is not working.
It is working with a necessary implemented getter.
const
myProxy = new Proxy({}, {
get: function(obj, prop) {
return 42;
}
}),
{ value } = myProxy;
console.log(myProxy.value);
console.log(value);

Brackets in ES6 JavaScript

I'm desperate for someone to give me just some concise information about when I should use which brackets where and why in JS ES6. I know the basics but when we start talking about arrow syntax I just lose it and then can't see why we're wrapping braces in brackets etc... I feel like in order to truly understand why we lay things out the way we do I need to first understand what all of the use cases are for both {} and ().
For example. I'm really struggling to work out syntax like this:
const func = (obj) => {
console.log(obj.a)
}
func({a: "blue"})
It's the func({a: "blue"}) part I'm struggling with here.
Here's another example:
makeSound({
a: "bark",
b: 2,
c: "hiss"
})
function makeSound(options)
console.log("the" + options.a + "was a " + options.c)
I don't know what to make of this. What are we doing at the top with makeSound? I can see we're making an object but then why aren't we just declaring it as a variable with standard let makeSound = {}. What are we actually doing here? Is makeSound nothing untill we make it into a function further down the code?
It's the func({a: "blue"}) part I'm struggling with here.
{a: "blue"} is an object literal. The resulting object is passed as an argument to func(...).
I can see we're making an object but then why aren't we just declaring it as a variable with standard let makeSound = {}.
Because it is only needed once.
let details = {
a: "bark",
b: 2,
c: "hiss"
};
makeSound(details);
… would give the same result, but now you've got a details variable you don't need any more.
Is makeSound nothing untill we make it into a function further down the code?
function declarations are hoisted so it is a function even though the declaration appears later on.
I understand your confusion as there are quite a lot of curly braces indeed!
First, objects.
You define an object using brackets like this.
const obj = { a: 1 };
But you can also define an object inline, directly in a function argument list, using an object literal like this:
myFunc({ a: 1 }); // same as myFunc(obj);
Then you have arrow functions.
Their bodies is defined using curly braces too, just as regular functions, in which case you have to use the return keyword if you want to return a value from your function:
const myFunc = (arg) => { return 'hello ' + arg; }
However, arrow function also support implicit return, if you omit the curly braces, the return value will be implicit:
const myFunc = (arg) => 'hello ' + arg;
Now, you can also use the curly braces for desctructuring assignment.
For example:
const { a } = { a: 1 };
Here the desctructuring happens to the left of = and it allows you to extract properties from objects and assign them to variables.
You can also use object destructuring in function arguments, to access specific properties, like so:
const myFunc = ({ name }) => 'Hello ' + name;
This is equivalent to:
const myFunc = (person) => 'Hello ' + person.name;
And you could call this function with an object literal like this:
myFunc({ name: 'Jo' });
const func = (obj) => {
console.log(obj.a)
}
(obj) is basically saying func function takes obj as an argument.
You can also write it as such if you are passing only one argument;
const func = obj => {
console.log(obj.a)
}
What the parenthesis does basically giving you the ability to add multiple arguments. So like below;
const func = (obj1, obj2) => {
console.log(obj1.a, obj2.a)
}
func({a: "blue"})
Next func({a: "blue"})
Basically here you are calling func function with an object as an argument as a short hand.
So you can call it also like this
const argument = {a: "blue"}
func(argument)
Also you might see a lot of this kind of code
const func = (obj1, obj2) => console.log(obj1.a, obj2.a)
See there aren't anymore the curly braces around the console.log(). You can omit curly braces when you have only one line in the function. When you have multiple lines you will need to use curly braces to wrap the function body like so
func = (obj) => {
if (obj.a === "blue") {
return true
}
return false
}

NodeJS: Deep copy a function

Is it possible to deep copy a function object in NodeJS? I am trying to use a function that I have set fields on, but I need a way to copy that function so that when I do duplicate it, I can modify these extra fields separately.
For example:
let a = function(){console.log('hello world')}
a.__var = 1
let b = clone(a)
a.__var // 1
b.__val // 1
b.__var = 2
a.__var // 1
I've tried things like using underscore/lodash, but they seem to convert the function to an object in the clone. b would wind up being { __var: 1 } in the previous example. I need to be able to perform a deep copy on the function..
Another approach to this that I've used is to .bind() the function (which produces a copy of the function) but not bind any actual arguments. If the function has static methods/properties on it, you can use Object.assign to copy those on. My use case for doing this was shimming the global Notification constructor. Example:
// copy the constructor
var NotifConstructor = Notification.bind(Notification);
//assign on static methods and props
var ShimmedNotif = Object.assign(function (title, _opts) { /* impl here that returns NotifConstructor */ }, Notification);
//now you can call it just like you would Notification (and Notification isn't clobbered)
new ShimmedNotif('test');
For simpler use cases, bind will probably work, e.g.:
function hi(name) { console.log('hey ' + name); }
var newHi = hi.bind();
newHi('you'); //=> 'hey you'
I was able to achieve the desired functionality by doing the following:
let a = function (){console.log('hello world')}
a.field = 'value'
// Wrap the "cloned" function in a outer function so that fields on the
// outer function don't mutate those of the inner function
let b = function() { return a.call(this, ...arguments) }
b.field = 'different value'
console.log(a.field === b.field) // false
Use lodash's _.assign(dest, src)
let a = function(){console.log('hello world')}
a.__var = 1
Then..
let b = () => 42;
_.assign(b,a);
b.__var // returns 1
b() // returns 42

Does a Javascript function return objects by reference or value by default?

I have an object defined outside the function, in a global scope. This object is not passed into the function as an argument, but the function does modify it and return the modified object.
What I wanted to know is, if the function returns a copy of the object, or the original global object?
Also, will passing that object to the function as an argument, make a difference, since objects are passed into functions by reference?
Whenever you're returning an object, you're returning a reference to the object. Likewise, when you're passing an object, you're passing a reference. However, passing an object in as an argument can be different than just changing an object in global scope, as these examples show. This is because the reference to the object is itself passed by value.
If you're changing the members of an object, then whether you pass it in as an argument or just update the global object makes no difference. Either way, you're working with the same object.
Example 1:
var object = {foo:'original'};
function changeObject() {
object.foo = 'changed';
return object;
}
console.log(changeObject()); // outputs {foo:'changed'}
console.log(object); // outputs {foo:'changed'}
Example 2:
var object = {foo:'original'};
function changeArgument(object) {
object.foo = 'changed';
return object;
}
console.log(changeArgument(object)); // outputs {foo:'changed'}
console.log(object); // outputs {foo:'changed'}
On the other hand, if you're overwriting the object with a new object, the change won't persist if you do it to the argument, but will persist if you do it to the global object. That's because the argument passes the reference to the object by value. Once you replace this value with a reference to a new object, you're not talking about the same object anymore.
Example 3:
var object = {foo:'original'};
function replaceObject() {
object = {foo:'changed'};
return object;
}
console.log(replaceObject()); // outputs {foo:'changed'}
console.log(object); // outputs {foo:'changed'}
Example 4:
var object = {foo:'original'};
function replaceArgument(object) {
object = {foo:'changed'};
return object;
}
console.log(replaceArgument(object)); // outputs {foo:'changed'}
console.log(object); // outputs {foo:'original'}
May be late comment, but this is typical challenge in any language.
Objects created on the heap and passed around by reference opposed to primitives(by value).
I think the root of the question is shared instance versus unique one to avoid unwelcome effects.
For example we calling a function to get a template(object) for new user to add to collection or want to clear the form
on cancel event from different modules to start over. It easy to understand and easy to overlook..test cases typically
not covering all usage permutations
The sanity checklist:
Here the shared instance:
var bigo = {
usr: { name: 'steven' },
bigi: function () {
return this.usr;
}
};
var outA = bigo.bigi();
var outB = bigo.bigi();
print(outA.name); // => steven
print(outB.name); // => steven
outA.name = 'ilan'; // change value
print(outA.name); // => ilan
print(outB.name); // => ilan
Non shared instance:
var bigo = {
bigi: function () {
var user = { name: 'steven' };
return user;
}
};
var outA = bigo.bigi();
var outB = bigo.bigi();
print(outA.name); // => steven
print(outB.name); // => steven
outA.name = 'ilan'; // change value
print(outA.name); // => ilan
print(outB.name); // => steven
What I wanted to know is, if the function returns a copy of the object, or the original global object?
Effectively, you only ever deal with references to objects in JavaScript. Even var foo = {} just assigns a reference to a new object to foo.
If the object is outside the function, you don't need to 'return' it. If you modify the object within the function it will update the object itself. Then you can reference the newly updated object in other functions as needed.
From your question this is how I think your code looks (more or less):
var o = {};
function f() {
o.prop = true;
return o;
}
In this case the global variable o references an object.
When you modify o you're modify whatever o references. Hence it modifies the original object.
When you return o you're returning a reference to the original object.
Passing the object to a function results in the reference to the original object being passed. Hence any modifications will affect the original object. For example:
var o = {};
f(o);
console.log(o.prop); // true
function f(o) {
o.prop = true;
}

Categories

Resources