Spread inside JSON reviver function - javascript

I Would like to be able to spread out the value of an entry inside a JSON.parse reviver function while also drop the original key.
Returning a spreaded value doesn't seem to do that trick.
here is an example:
const json = {a: 'foo', b: {c: 1, d: '2'}}
const stringify = JSON.stringify(json)
const parsed = JSON.parse(stringify, function(k, v) {
if(k === 'b') {
return {...v}
} else {
return v
}
})
document.getElementById("app").innerHTML = `<pre>${JSON.stringify(parsed, null, 2)}</pre>`;
<div id="app"></div>
In the above the desired output should be
{
"a": "foo",
"c": 1,
"d": "2"
}

While I wouldn't recommend it as it's not very intuitive, you can use Object.assign(this, v) to merge with the this value in your replacer. This merges object at the b property into the object that the b property appears in, temporarily giving you:
{a: 'foo', b: {c: 1, d: '2'}, c: 1, d: '2'}
and then b is removed as we return undfined for that path, giving:
{a: 'foo', c: 1, d: '2'}
const json = {a: 'foo', b: {c: 1, d: '2'}};
const stringify = JSON.stringify(json)
const parsed = JSON.parse(stringify, function(k, v) {
if(k === 'b') {
Object.assign(this, v);
} else {
return v;
}
});
document.getElementById("app").innerHTML = `<pre>${JSON.stringify(parsed, null, 2)}</pre>`;
<div id="app"></div>

JSON.parse reviver only transform the value that is at key "b" and set it on that key. I would picture it similar to a map.
Your solution should be outside the reviver function. Here is a sample of how your code could look like:
let { b, ...parsed } = JSON.parse(stringify);
if (b) {
parsed = { ...parsed, ...b };
}

#Geo Mircean answer is correct, but your comment add new information to the question.
If you have a complex structure, I don't think there is an easier option. The first one that comes to mind is to iterate the object and see if you need to spread each field. The logic to tell if you need to spread is up to you, but it will be something like:
let parsed = {...jsonObject};
for(let key in jsonObject)
{
if (typeof jsonObject[key] === 'object') // if you want to spread
{
delete parsed[key]; // delete the actual key
Object.assign(parsed, jsonObject[key]); // add fields to object
}
}
Notice that the parsed variable will have our result. At first it get the whole object, then we iterate over the result, delete the field and spread the field value itself.
The Object.assign came from this StackOverflow question and does not work in IE9 (but I personally don't care about that).

Related

Javascript recursive function on nested object

We have the following kind of object:
const obj = {
a: { "=": 0 },
b: {
c: { "=": 1 },
d: {
e: { "=": 2 },
f: {
g: { "=": 3 }
}
}
}
}
I am trying to write a function that returns an array with the object keys, including the nested ones, but groups them if they are nested and their values is not { "=": "something" }. To better explain I'll write a sample output desired for the object above:
["a", "b.c", "b.d.e", "b.d.f.g"]
All I have achieved for now is a function that only traverses the first level, but I admit I got stuck on the recursive part where the same function will apply on every level of the object until it reaches an entry of type { "=": "something" }. Follows my actual code:
function nestedObject(obj) {
const operator = "="
const attributes = []
void (function traverse(obj) {
if (isObject(obj)) { // <-- isObject(obj) just checks if typeof obj == "object", !Array.isArray(obj), obj !== null
for (const [k,v] of Object.entries(obj)) {
let attr = k
for (const [k_,v_] of Object.entries(v)) {
if (operator !== k_) {
attr += "." + k_
// here is where the recursive part should start
// but I didn't figure out yet how to make it right.
// Of course calling traverse(v_) doesn't give the desired result.
}
attributes.push(attr)
}
}
}
})(obj)
console.log(attributes) // <-- will output ["a", "b.c", "b.d"]
}
I hope I did explain well enough. I don't even know if this is possible, but I think it is. Would love to hear some ideas.
Here's a small generator function that, given an object, recursively yields pairs like [ array-of-nested-keys, value ]:
function* enumRec(obj, keys=[]) {
yield [keys, obj];
if (obj && typeof obj === 'object')
for (let [k, v] of Object.entries(obj))
yield *enumRec(v, keys.concat(k))
}
To solve the problem at hand, iterate enumRec and collect keys that end with a =:
for (let [keys, _] of enumRec(obj)) {
if (keys.at(-1) === '=')
console.log(keys.slice(0, -1).join('.'))
}
I keep handy some utility functions to make such coding easier. One of them takes an object and lists all the leaf paths in that object, as an array of keys. Building your function atop that becomes simply a matter of removing trailing = parts and joining the results with dots:
const getPaths = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (([k, v]) => getPaths (v) .map (p => [k, ...p]))
: [[]]
const nestedObject = (obj) =>
getPaths (obj)
.map (p => p .at (-1) == '=' ? p .slice (0, -1): p)
.map (p => p .join ('.'))
const obj = {a: {"=": 0}, b: {c: {"=": 1}, d: {e: {"=": 2}, f: {g: {"=": 3}}}}}
console .log (nestedObject (obj))
This is a slightly less sophisticated version of getPaths than I usually write. Usually I distinguish between numeric array indices and string object keys, but that's not relevant here, so this version is simplified.
The main function should be clear enough on top of that.
Note that this does not prohibit internal = keys, so if, parallel to g in your input, you also had an h with an = key, like this:
f: {
g: { "=": 3 },
h: { "=": {i: { "=": 4}}}
}
we would yield this result:
["a", "b.c", "b.d.e", "b.d.f.g", "b.d.f.h.=.i"]
If you also wanted to remove those, you could add this between the two map calls:
.filter (p => ! p.includes ('='))

Is there a way to get all keys inside of an object (including sub-objects)? [duplicate]

This question already has answers here:
Get all keys of a deep object in Javascript
(7 answers)
Closed 3 years ago.
Currently, I'm trying to get all the keys of an object, including the sub-objects.
I know that
Object.keys(obj)
returns the keys of the object, but not the keys of the objects inside of it.
So, for example, I got following object:
let exampleObject =
{
a: "value",
b: "value",
c: 404,
d: {
e: "value",
f: "value"
}
}
NOTE: The d key might change its name, so it won't work to use something like Object.keys(obj.d).
How do I get e and f in my total list of keys existing inside an object?
You could use flatMap to recursively get the keys like this:
let exampleObject={a:"value",b:"value",c:404,d:{e:"value",f:"value"}};
const getKeys = obj => Object.keys(obj).flatMap(k => Object(obj[k]) === obj[k]
? [k, ...getKeys(obj[k])]
: k)
console.log(getKeys(exampleObject))
If flatMap is not supported, use reduce like this:
function getKeys(obj) {
return Object.keys(obj).reduce((r, k) => {
r.push(k);
if(Object(obj[k]) === obj[k])
r.push(...getKeys(obj[k]));
return r;
}, [])
}
The Object(obj[k]) === obj[k] checks if the property is an object and it is not null. Because, typeof null === "object"
You can try that using recursion.
Create a wrapper function getAllKeys() and create an empty array inside that.
Now create another function getKeys() which takes object and which will be called recursively.
Inside getKeys() loop through the keys using for..in.
push() the key into empty array created in wrapper function.
Check if the typeof key is "object" then call the function recursively on that.
let exampleObject =
{
a: "value",
b: "value",
c: 404,
d: {
e: "value",
f: "value"
}
}
function getAllKeys(obj){
let res = []
function getKeys(obj){
for(let key in obj){
res.push(key);
if(typeof obj[key] === "object"){
getKeys(obj[key])
}
}
}
getKeys(obj);
return res;
}
console.log(getAllKeys(exampleObject))

Javascript - any kind of rest operator that skips undefined properties?

I have this:
var filters = {a: 1, b: undefined}
And I want this:
{ a:1 }
Is there any way of getting the object above by using modern Javascript features like {...filters} ?
Unless you know in advance a singular property which is undefined, it's not possible with rest syntax, or anything new like that, though you can achieve it by serializing and deserializing the object. The code is short, but it's inelegant and only works when everything in the object is serializable:
var filters = {a: 1, b: undefined};
var filtersNoUndef = JSON.parse(JSON.stringify(filters));
console.log(filtersNoUndef);
Or, of course, you can iterate over the properties manually:
var filters = {a: 1, b: undefined};
const filtersNoUndef = { ...filters };
Object.entries(filtersNoUndef).forEach(([key, val]) => {
if (val === undefined) {
delete filtersNoUndef[key];
}
});
console.log(filtersNoUndef);
An alternative using the function reduce
let filters = {a: 1, b: undefined};
let result = Object.entries(filters).reduce((a, [key, value]) => value === undefined ? a : Object.assign(a, {[key]: value}), Object.create(null));
console.log(result);

Extending object's properties without overwriting them

I'm trying to extend the keys/values in target object (with the keys/values) from source object, but without overwriting existing keys/values in the target object. Meaning:
var obj1 = {
a: 1,
b: 2
};
var obj2 = {
b: 4,
c: 3
};
extend(obj1, obj2);
console.log(obj1); // --> {a: 1, b: 2, c: 3}
Interestingly, I found Object.assign(obj1,obj2);, but it overwrites the keys/values.
I need to not overwrite them, if existent and add them if nonexistent.
Please help in plain JavaScript.
Just a simple loop. If you only want enumerable own properties, then:
Object.keys(obj2).forEach(function(key) {
if (!(key in obj1)) {
obj1[key] = obj2[key];
}
});
If you want all enumerable properties:
var key;
for (key in obj2) {
if (!(key in obj1)) {
obj1[key] = obj2[key];
}
}
The key (no pun) bit there is the in operator, which tells you whether an object has a property (of its own, or via inheritance).
There's also Object.prototype.hasOwnProperty which would tell you only if the object has its own (not inherited) property with a given name:
Object.keys(obj2).forEach(function(key) {
if (!obj1.hasOwnProperty(key)) {
obj1[key] = obj2[key];
}
});
More:
in operator
hasOwnProperty
There is no such built in functions available in JS to do that for you.
You have to write your own logic to do that,
var x = {a:10};
var y = {a:5, b: 20};
merge(x,y);
function merge(objSrc, objTarget){
return Object.keys(objTarget).reduce(function(src, prop){
if(!src.hasOwnProperty(prop)) src[prop] = objTarget[prop];
return src;
}, objSrc);
}
console.log(x); {a:10, b:20}
P.S The above code would do a merge over enumerable own properties since Object.keys() would return the same.

Javascript - removing undefined fields from an object [duplicate]

This question already has answers here:
Remove blank attributes from an Object in Javascript
(53 answers)
Closed 5 years ago.
Is there a clean way to remove undefined fields from an object?
i.e.
> var obj = { a: 1, b: undefined, c: 3 }
> removeUndefined(obj)
{ a: 1, c: 3 }
I came across two solutions:
_.each(query, function removeUndefined(value, key) {
if (_.isUndefined(value)) {
delete query[key];
}
});
or:
_.omit(obj, _.filter(_.keys(obj), function(key) { return _.isUndefined(obj[key]) }))
A one-liner using ES6 arrow function and ternary operator:
Object.keys(obj).forEach(key => obj[key] === undefined ? delete obj[key] : {});
Or use short-circuit evaluation instead of ternary: (#Matt Langlois, thanks for the info!)
Object.keys(obj).forEach(key => obj[key] === undefined && delete obj[key])
Same example using if statement:
Object.keys(obj).forEach(key => {
if (obj[key] === undefined) {
delete obj[key];
}
});
If you want to remove the items from nested objects as well, you can use a recursive function:
const removeEmpty = (obj) => {
let newObj = {};
Object.keys(obj).forEach((key) => {
if (obj[key] === Object(obj[key])) newObj[key] = removeEmpty(obj[key]);
else if (obj[key] !== undefined) newObj[key] = obj[key];
});
return newObj;
};
I prefer to use something like Lodash:
import { pickBy, identity } from 'lodash'
const cleanedObject = pickBy(originalObject, identity)
Note that the identity function is just x => x and its result will be false for all falsy values. So this removes undefined, "", 0, null, ...
If you only want the undefined values removed you can do this:
const cleanedObject = pickBy(originalObject, v => v !== undefined)
It gives you a new object, which is usually preferable over mutating the original object like some of the other answers suggest.
Use JSON Utilities
Overview
Given an object like:
var obj = { a: 1, b: undefined, c: 3 }
To remove undefined props in an object we can use nested JSON methods stringify and parse like so:
JSON.parse(JSON.stringify(obj))
Live Example
var obj = { a: 1, b: undefined, c: 3 }
var output = JSON.parse(JSON.stringify(obj));
console.log(output)
Limitations and warnings
Depending on how Javascript is implemented.
It is possible that undefined will be converted to null instead of just being removed.
Nested Object, Array will be converted to strings
Date, time values also converted to strings
Tested
The above code was tested in Firefox, Chrome, and Node 14.18.1 and removed "b" from all obj arrays. Still I recommend exercising caution using this method unless you are in a stable environment (such as cloud functions or docker) I would not rely on this method client side.
Because it doesn't seem to have been mentioned, here's my preferred method, sans side effects or external dependencies:
const obj = {
a: 1,
b: undefined
}
const newObject = Object.keys(obj).reduce((acc, key) => {
const _acc = acc;
if (obj[key] !== undefined) _acc[key] = obj[key];
return _acc;
}, {})
console.log(newObject)
// Object {a: 1}
This solution also avoids hasOwnProperty() as Object.keys returns an array of a given object's own enumerable properties.
Object.keys(obj).forEach(function (key) {
if(typeof obj[key] === 'undefined'){
delete obj[key];
}
});
and you can add this as null or '' for stricter cleaning.
Here's a plain javascript (no library required) solution:
function removeUndefinedProps(obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && obj[prop] === undefined) {
delete obj[prop];
}
}
}
Working demo: http://jsfiddle.net/jfriend00/djj5g5fu/
Mhh.. I think #Damian asks for remove undefined field (property) from an JS object.
Then, I would simply do :
for (const i in myObj) {
if (typeof myObj[i] === 'undefined') {
delete myObj[i];
}
}
Short and efficient solution, in (vanilla) JS !
Example :
const myObj = {
a: 1,
b: undefined,
c: null,
d: 'hello world'
};
for (const i in myObj) {
if (typeof myObj[i] === 'undefined') {
delete myObj[i];
}
}
console.log(myObj);
This one is easy to remember, but might be slow. Use jQuery to copy non-null properties to an empty object. No deep copy unless you add true as first argument.
myObj = $.extend({}, myObj);
Another Javascript Solution
for(var i=0,keys = Object.keys(obj),len=keys.length;i<len;i++){
if(typeof obj[keys[i]] === 'undefined'){
delete obj[keys[i]];
}
}
No additional hasOwnProperty check is required as Object.keys does not look up the prototype chain and returns only the properties of obj.
DEMO

Categories

Resources