remove JSON parent property only if child has empty [] JavaScript - javascript

I would like to remove any parent JSON item if the child has empty [], not if the child is empty. The nested JSON has arrays of JSON as well
Here is an example JSON:
{
"hi": 596,
"hello": {
"a" : []
},
"h": {
"a" : ""
},
"hey" : {
"a" : 293,
"b" : "23",
"c" : {
"1" : []
},
"d" : [{
"1" : {
"z" : []
},
"2" : "123"
}]
},
"hola" : 123
}
How I would like the output to look like:
{
"hi": 596,
"h": {
"a" : ""
},
"hey" : {
"a" : 293,
"b" : "23",
"d" : [{
"2" : "123"
}]
},
"hola" : 123
}
How can I do this?
I tried regex JSON.stringify(data).replace(/,\\n\s*\\"(.*?)\\":\s*\[\]*,/g, "") but I believe this only matched the child node. Are there better ways to go about this?

This might require a bit of optimization, but you can use recursion to achieve your goal
const input = {
"hi": 596,
"hello": {
"a": []
},
"h": {
"a": ""
},
"hey": {
"a": 293,
"b": "23",
"c": {
"1": []
}
},
"hola": 123
};
function removeEmpty(obj, parent = null, finalResult = {}) {
Object.keys(obj).forEach(function(key) {
const objKey = obj[key];
if (objKey.constructor === Array && objKey.length === 0) {
return
}
if (typeof objKey === 'object') {
return removeEmpty(objKey, key, finalResult);
} else {
if (parent) {
if (!finalResult[parent]) {
finalResult[parent] = {}
}
finalResult[parent][key] = objKey
} else {
finalResult[key] = objKey;
}
}
});
return finalResult;
}
const output = removeEmpty(input);
console.log(output);

Your question is a bit vague (what should happen if only some children are empty?) and hence I'd recommend using a library. That will make it easier to iterate on your solution as edge cases pop up. Here is how you could solve it using object-scan
// const objectScan = require('object-scan');
const data = { hi: 596, hello: { a: [] }, h: { a: '' }, hey: { a: 293, b: '23', c: { 1: [] }, d: [{ 1: { z: [] }, 2: '123' }] }, hola: 123 };
const prune = objectScan(['**.*.*'], {
rtn: 'count',
filterFn: ({ gparent, gproperty, value }) => {
if (Array.isArray(value) && value.length === 0) {
delete gparent[gproperty];
return true;
}
return false;
}
});
console.log(prune(data));
// => 3
console.log(data);
// => { hi: 596, h: { a: '' }, hey: { a: 293, b: '23', d: [ { '2': '123' } ] }, hola: 123 }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#15.0.0"></script>
Disclaimer: I'm the author of object-scan

Related

How to reduce an array of objects into one object with unique properties? JavaScript

How can I reduce an array of objects into one object, with unique properties. I will appreciate any help! Thank you!
const input =
[ { "a": false}
, { "b": false, "c": true }
, { "b": false, "c": true }
, { "a": false }
, { "b": false, "c": true }
, { "b": false, "c": true }
, { "b": false, "c": true, "b": false }
]
// I tried :
const object =
input.reduce( (obj, item) =>
Object.assign(obj, { [item.key]: item.value })
, {});
console.log( object );
but I get:
{ "undefined": undefined }
Expected result:
{"a":false,"b":false,"c":true}
let input = [
{ a: false },
{ b: false, c: true },
{ b: false, c: true },
{ a: false },
{ b: false, c: true },
{ b: false, c: true },
{ b: false, c: true, b: false },
];
let result = input.reduce((prev, curr) => {
Object.assign(prev, curr);
return prev;
}, {});
console.log(result);
As you can tell, by using the Array.reduce() method, we can reduce the array to an object by initializing the reducer using an empty object ({}) as the second argument of Array.reduce().
And then in the reducer callback, we can work our way up to build the object using the Object.assign() method like how we initially wanted as an end result.
something like this:
const inputObject = input.reduce(
(previousObject, currentObject) => {
return Object.assign(previousObject, currentObject);
},
{});
console.log(inputObject);

How to unpack array inside of array of objects?

So I am trying to automate unpacking a nested Json with arrays inside and stuck at creating duplicate objects if value of key is array with length > 1
How do I do it?
Right now I am trying to achieve it with recursion
[
{
a: '1',
b: [
{
c: '3',
d: '4',
},
{
c: '5'
},
{
c: '7',
d: '8'
}
],
f: [
{
d: '6'
},
{
d: '9'
}
],
e: [
{
g: '9'
}
]
}
]
// Expect
// When creating duplicate object, those keys which repeat I want to mark as undefined, to make JSON lighter
// I also want to add 'key: number' to order those objects
[
{
a: '1',
b.c: '3',
b.d: '4',
f.d: '6',
e.g: '9',
key: 1,
},
{
a: undefined,
b.c: '5',
b.d: undefined,
f.d: '9',
e.g: undefined,
key: 2,
},
{
a: undefined,
b.c: '7',
b.d: '8',
f.d: undefined,
e.g: undefined,
key: 3,
}
]
// Code
function recurseObject(object: any, nestedKeyName: any, obj: any, count: any) {
Object.entries(object).map(([key, dataItem]: [key: string, dataItem: any]) => {
const newKeyName = nestedKeyName ? (nestedKeyName + '.' + key) : key
let currentCount = count
if (Array.isArray(dataItem)) {
obj[newKeyName] = dataItem.map((item: any) => {
const objNested: any = {}
recurseObject(item, newKeyName, objNested, currentCount)
return objNested
})
} else if (isObject(dataItem)) {
obj['key'] = currentCount
recurseObject(dataItem, newKeyName, obj, currentCount + 1)
} else {
obj[newKeyName] = dataItem
}
})
}
function rcBody(data: any): any {
if (Array.isArray(data)) {
let key = 0
return data.map((object: any) => {
const obj: any = {}
recurseObject(object, null, obj, 0)
obj['key'] = key
key += 1
return obj
})
} else if (isObject(data)) {
const obj: any = {}
recurseObject(data, null, obj, 0)
return obj
} else {
return {}
}
}
If the value of key is array of objects with more than one object, then I want to create a duplicate object.
Table I want to generate
I have bad news and good news for you:
First the bad news:
If it is in fact true, that objects/arrays can be nested even further than it is currently the case, then your data saving format doesn't really work. Let me show you why wiht the help of an example:
[
{
t: [
{
a: [
{
b: 1,
c: 2,
},
{ c: 3 },
],
},
{
a: [
{
b: 3,
c: 5,
},
],
},
],
},
];
how would you go about storing this? ... without nesting the return Array/object itself, it is extremely difficult (if not impossible), to store the date how you are wanting to)
How do you want to go about storing cases like this one here? (The example I provided is a quite tame example ... if you add some more nesting/ more objects/ arrays, it gets way more complicated.
Also is the your particular way of storing data even required? Is the way you structure your desired return array/objects relevant in future steps, or would other structures work too?
But nonetheless I have produced 2 functions ... the first one can handle 2 deep paths (so no deeper than the array/object you provided in your example) the array above would NOT work with this function
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction2(arg, currentPath = "", rtnArr = [{}], key = 0) {
if (Array.isArray(arg)) {
arg.forEach((x, index) => myFunction2(x, currentPath, rtnArr, index));
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction2(arg[x], currentPath + "." + x, rtnArr, key));
} else {
rtnArr[key] = rtnArr[key] || {};
rtnArr[key][currentPath.substring(1)] = arg;
}
return rtnArr;
}
console.log(myFunction2(x));
The next function can handle infinitely nested arrays/objects - BUT the return is not quite like you desire - (but still: all paths with value are still present in an array of objects ... and each objects, contains each unique path only once) - only in which object the path value pair appears is different. But it's probably best to just test/read the code to see how it works, what the output will be.
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction(arg, currentPath = "", rtnArr = [{}]) {
if (Array.isArray(arg)) {
arg.forEach((x) => myFunction(x, currentPath, rtnArr));
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction(arg[x], currentPath + "." + x, rtnArr));
} else {
for (let i = 0; i < rtnArr.length; i++) {
if (!rtnArr[i][currentPath.substring(1)]) {
rtnArr[i][currentPath.substring(1)] = arg;
return rtnArr
}
}
rtnArr[rtnArr.length] = {};
rtnArr[rtnArr.length - 1][currentPath.substring(1)] = arg;
}
return rtnArr;
}
console.log(myFunction(x))
At the very least, you know have an idea, how you can solve the problem with recursive functions ... and if my functions don't quite fit your purpose, you at least have a working start point, from where you can tweak and improve the functions, to a point where they fit your needs ... or take the knowledge/ideas you get from reading and understanding my code to write your own.
Edit:
Great news ... I figured out a way where you can have your structure/sorting of data and no restriction on how deeply nested the passed array/object can be.
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction2(arg, currentPath = "", rtnArr = [{}], key = []) {
if (Array.isArray(arg)) {
arg.forEach((x, index) => {
key.push(index);
myFunction2(x, currentPath, rtnArr, key);
key.pop();
});
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction2(arg[x], currentPath + "." + x, rtnArr, key));
} else {
let stringKey = key.reduce((pVal, cVal) => pVal + "." + cVal, "");
if (rtnArr.some((x) => x.key === stringKey)) {
rtnArr.filter((x) => x.key === stringKey)[0][currentPath] = arg;
} else {
rtnArr[rtnArr.length] = { key: stringKey };
rtnArr[rtnArr.length - 1][currentPath] = arg;
}
}
return rtnArr;
}
console.log(myFunction2(x));

delete part of an object and place it in another object

I have an object (values). I want to select the field which startWith(')and which contains an object(for this case the 2). I want to delete the entire object from the createValue array and place it in an object CreateFileValue. You can check the output
Example input:
const values = {
ID: ,
c: [{
f: "",
v: 'hello',
},
{
f: "102",
fi: "doc.pdf",
v: {
f: 'F2',
fi: '',
v: 'jkhkjhkhkjh'
},
}
]
}
Example output:
const values = {
ID:817,
c: [{
f: "F",
v: 'hello',
}
],
c: {
"f": "F",
"fi": "ff.pdf",
"v": "jkhkjhkhkjh"
}
}
const values = {
ID: 7,
c: [{
f: "FL",
v: 'hello',
},
{
f: "F2",
fi: "doc.pdf",
v: {
f: '',
fi: '.pdf',
v: 'jkhkjhkhkjh'
},
}
]
}
const res = () => {
if (values.calues.startsWith('') && typeof values.cralues.startsWith('F') === 'object') {
return {
};
}
}
};
console.log(res());
const values = {
ID: 748817,
createValues: [{
field: "FLD_STR_101",
value: 'hello',
},
{
field: "FLD_STR_102",
fileName: "doc.pdf",
value: {
field: 'FLD_STR_102',
fileName: 'bulletin_paie_avril.pdf',
value: 'jkhkjhkhkjh'
},
}
]
}
// build createFileValues structure
values.createFileValues = values.createValues.filter(v => v.field.startsWith("FLD_STR_") && typeof v.value === "object");
// filter the files out of createValues
values.createValues = values.createValues.filter(v => !values.createFileValues.includes(v));
console.log(values);
values.createValues.startsWith('FLD_STR_') isn't valid because createValues is an array. You'll need to iterate over values.createValues in order to check each field.startsWith('FLD_STR_') and if value is an object (see How to detect if a variable is a pure javascript object for an example of this).
const values = {
ID: 748817,
createValues: [{
field: "FLD_STR_101",
value: 'hello',
},
{
field: "FLD_STR_102",
fileName: "doc.pdf",
value: {
field: 'FLD_STR_102',
fileName: 'bulletin_paie_avril.pdf',
value: 'jkhkjhkhkjh'
}
}
]
}
const res = () => {
var newResult = {
ID: values.ID,
createValues: []
};
values.createValues.forEach(function(item) {
var newCreateValue = {};
if (item.field.startsWith('FLD_STR_') && item.value.constructor.name === "Object") {
newCreateValue.field = item.value.field;
newCreateValue.value = item.value.value;
newCreateValue.fileName = item.value.fileName;
// if this is a createFilesValue, add it as a new key/value to the newResult object
newResult.createFileValues = newCreateValue;
} else {
newCreateValue.field = item.field;
newCreateValue.value = item.value;
// if this is an existing createValues, add it to the createValues array
newResult.createValues.push(newCreateValue);
}
});
return newResult;
};
console.log(res());
This should work
const values = {
ID: 748817,
createValues: [{
field: "FLD_STR_101",
value: 'hello',
},
{
field: "FLD_STR_102",
fileName: "doc.pdf",
value: {
field: 'FLD_STR_102',
fileName: 'bulletin_paie_avril.pdf',
value: 'jkhkjhkhkjh'
},
}
]
}
const res = (data) => {
let oldCreateValues = data.createValues;
let oldCreateFileValues = data.createFileValues;
let newCreateValues = [];
let newCreateFileValues = oldCreateFileValues && Array.isArray(oldCreateFileValues) ? oldCreateFileValues : [];
if (oldCreateValues && Array.isArray(oldCreateValues)) {
oldCreateValues.forEach(item => {
if (item.field.startsWith('FLD_STR_') && item.value && typeof item.value === 'object') {
newCreateFileValues.push(item);
} else {
newCreateValues.push(item);
}
});
}
data.createValues = newCreateValues;
data.createFileValues = newCreateFileValues;
return data;
};
console.log(res(values));

how to combine array of object fast or in optimized way

I have two Array of objects [{id:'KS001', name: 'albedo'}] this is the first array which consist of 900+ objects.
{
"changePoints": [
{
"Point": {
"name": "001",
"id": "KS001",
"siteID": "258628",
"connectorGroups": [
{
"connectorGroupID": 1,
"connectors": [
{
"connectorID": "1",
"connectorStatus": "AVAILABLE"
}
]
},
{
"connectorGroupID": 2,
"connectors": [
{
"connectorID": "2",
"connectorStatus": "AVAILABLE"
}
]
}
]
}
},
],
}
this is the second array that contains objects which have a point and id this id map with the first array as an identifier I need to check each connectorStatus in the second array with the corresponding id and add a new key to the first array status set "AVAILABLE" else not "NOT AVAILABLE"
I need to find the fastest way to do this
const object1 = {
name: 'Flavio'
}
const object2 = {
age: 35
}
const object3 = {...object1, ...object2 } //{name: "Flavio", age: 35}
const a = { b: 1, c: 2 };
const d = { e: 1, f: 2 };
const ad = { ...a, ...d }; // { b: 1, c: 2, e: 1, f: 2 }

How to reduce an array while merging one of it's field as well

I have this,
var o = [{
"id": 1, // its actually a string in real life
"course": "name1",
// more properties
},
{
"id": 1, // its actually a string in real life
"course": "name2",
// more properties
}];
I want this,
var r = [{
"id": 1, // its actually a string in real life
"course": ["name1", "name2"],
}];
I am trying this,
var flattened = [];
for (var i = 0; i < a.length; ++i) {
var current = a[i];
if(flattened.)
}
but I am stuck, I am not sure what to do next, array will have more then 2 records but this was just an example.
THERE are more fields but I removed them for simplicity, I won't be using them in final array.
You could reduce the array and find the object.
var array = [{ id: 1, course: "name1" }, { id: 1, course: "name2" }],
flat = array.reduce((r, { id, course }) => {
var temp = r.find(o => id === o.id);
if (!temp) {
r.push(temp = { id, course: [] });
}
temp.course.push(course);
return r;
}, []);
console.log(flat);
The same by taking a Map.
var array = [{ id: 1, course: "name1" }, { id: 1, course: "name2" }],
flat = Array.from(
array.reduce((m, { id, course }) => m.set(id, [...(m.get(id) || []) , course]), new Map),
([id, course]) => ({ id, course })
);
console.log(flat);
This way you will get the data flattened in the shape you want
const o = [
{
id: 1,
course: "name1"
},
{
id: 1,
course: "name2"
},
{
id: 2,
course: "name2"
}
];
const r = o.reduce((acc, current) => {
const index = acc.findIndex(x => x.id === current.id);
if (index !== -1) {
acc[index].course.push(current.course);
} else {
acc.push({id:current.id, course: [current.course]});
}
return acc
}, []);
console.log(r);
You can do this with reduce and Object.entries. This example works for any number of properties:
const o = [
{ id: 1, course: 'name1', time: 'morning', topic: 'math' },
{ id: 1, course: 'name2', time: 'afternoon' },
{ id: 2, course: 'name3', time: 'evening' }
];
const result = o.reduce((out, { id, ...rest }) => {
out[id] = out[id] || {};
const mergedProps = Object.entries(rest).reduce((acc, [k, v]) => {
return { ...acc, [k]: [...(out[id][k] || []), v] };
}, out[id]);
out[id] = { id, ...mergedProps };
return out;
}, {});
console.log(result);
If you only care about the id and course fields, you can simplify to this:
const o = [
{ id: 1, course: 'name1', time: 'morning', topic: 'math' },
{ id: 1, course: 'name2', time: 'afternoon' },
{ id: 2, course: 'name3', time: 'evening' }
];
const result = o.reduce((out, { id, course }) =>
({ ...out, [id]: { id, course: [...((out[id] || {}).course || []), course] } })
, {});
console.log(result);
You could use .reduce to create an object of keys, and then use that object to set keys to be of the id. This way you can add to the same course array by targetting the id of the object. Lastly, you can get the values of the object to get your result.
See example below:
var o = [{
"id": 1,
"course": "name1",
"foo": 1
},
{
"id": 1,
"course": "name2",
"bar": 2
}];
var res = Object.values(o.reduce((acc, {id, course, ...rest}) => {
if(id in acc)
acc[id] = {...acc[id], course: [...acc[id].course, course], ...rest};
else acc[id] = {id, course: [course], ...rest};
return acc;
}, {}));
console.log(res);
function merge(array, key = 'id') {
const obj = {}
for(const item of array) {
const existing = obj[item[key]]
if(existing) {
for(const [name, value] of Object.entries(item)) {
if(name === key) continue;
if(existing[name]) {
existing[name] = [ ...(existing[name].$custom ? existing[name] : [existing[name]]), value ]
existing[name].$custom = true;
} else {
existing[name] = value;
}
}
} else {
obj[item[key]] = { ...item }
}
}
return Object.values(obj)
}
var o = [
{
"id": 1,
"single": "test"
},
{
"id": 1,
"course": "name1",
"multifield": "test"
},
{
"id": 1,
"course": "name2"
},
{
"id": 1,
"newfield": "test"
}, {
"id": 2,
"anotherid": "test",
"array": [1,3,4]
}, {
"id": 2,
"array": "text"
}];
console.log(merge(o))
You can use reduce to accumulate the results. Search in the current result (accumulator a) for an object (el) with the same id, if found, append course to existing object and return the same accumulator, otherwise put into the accumulator with course as an array.
var res = o.reduce((a, {id,course}) => {
var found = a.find(el => el.id == id);
return found ? found.course.push(course) && a : [...a, {id, course: [course]}];
}, []);

Categories