Turning 2 level JSON to multiple 1 level JSON in Node.js - javascript

I have problems with writing a universal function in node that would parse JSON like this:
{
"parserId": 1,
"filters": [
{
"filterName": "replaceTitle",
"regex": "..."
},
{
"filterName": "replaceRegion",
"regex": "..."
}
]}
into multiple JSON like this:
{ "parserId": 1, "filterName": "replaceTitle","regex": "..." },{ "parserId": 1, "filterName": "replaceRegion", "regex": "..."}
It would be great if this function would be universal so doesn't matter what are the names of the fields in JSON, as long as it's build the same way.
Is there any node package already doing it? Thank you for your help!

You could map every item of the array and append an object with parserId with the item of the array.
var object = { parserId: 1, filters: [{ filterName: "replaceTitle", regex: "..." }, { filterName: "replaceRegion", regex: "..." }] },
array = object.filters.map(o => Object.assign({ parserId: object.parserId }, o));
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A more general approach could be the check if a property is an array and loop that array later or add the property to a common object, which keeps the same values for all new generated objects.
Later iterate the arrays and add the content to the objects as well.
This works without specifying the properties who are arrays or just values.
function unnormalize(object) {
var common = {};
return Object
.keys(object)
.filter(k => Array.isArray(object[k]) || (common[k] = object[k], false))
.reduce((r, k) => (object[k].forEach((o, i) => Object.assign(r[i] = r[i] || Object.assign({}, common), o)), r), []);
}
var object = { parserId: 1, filters: [{ filterName: "replaceTitle", regex: "..." }, { filterName: "replaceRegion", regex: "..." }] };
console.log(unnormalize(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use square bracket syntax to provide the name of the array. Then you can use map to transform the array, and Object.assign to merge the objects
let data = {
"parserId": 1,
"filters": [
{
"filterName": "replaceTitle",
"regex": "..."
},
{
"filterName": "replaceRegion",
"regex": "..."
}
]};
function format(data) {
let array = data.filters;
// Remove the array so that it's not duplicated
delete data.filters;
return array.map((item) => Object.assign(item, data));
}
console.log(format(data));

Related

How to rename object key based on a string of both properties and indexes generated by JSONPath

Sorry for the potentially confusing question. What I mean is this:
I've been tasked to rename a specific set of object keys on a massively nested JSON object I've converted from XML. These keys are scattered throughout the nested object, buried under both arrays and objects. Let me show you what I mean:
const obj = {
"Zone": [
{
ExtWall: [
{ Win: "10" }
],
HVACDist: {
HVACCool: [
{ Win: "10" }
]
}
}
]
};
In this case, I need to rename the key Win to ResWin. However, this tag Win is scattered across a nested object far deeper than this.
I'm currently using JSONPath to find the path of these keys. For example:
var WinPath = jp.paths(object, '$..Win');
// Returns - [
// [ '$', 'Zone', 0, 'ExtWall', 0, 'Win' ],
// [ '$', 'Zone', 0, 'HVACDist', 'HVACCool', 0, 'Win' ]
// ]
JSONPath also has a stringify function which generates a path based on the paths array it produces. Let's focus on one element for now:
const pathStringified = jp.stringify(WinPath[0])
// Returns - $['Zone'][0]['ExtWall][0]['Win']
Somehow, I need to rename this Win key to ResWin.
Right now my best guess is to replace the $ with an empty string so I can access the objec for key replacement.
My goal:
obj['Zone'][0]['ExtWall][0]['ResWin'] = obj['Zone'][0]['ExtWall][0]['Win'];
delete obj['Zone'][0]['ExtWall][0]['Win'];
Any feedback or suggestions would be much appreciated.
Also, let me know how to restructure my question because I can understand how it would be difficult to understand.
Sounds like you're ok using a library, so here is a solution using object-scan.
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.3.11/lib/index.min.js';
const obj = { Zone: [{ ExtWall: [{ Win: '10' }], HVACDist: { HVACCool: [{ Win: '10' }] } }] };
const modify = objectScan(['**.Win'], {
filterFn: ({ parent, property, value }) => {
delete parent[property];
parent.ResWin = value;
},
rtn: 'count'
});
const r = modify(obj);
console.log(r); // number of matches
// => 2
console.log(obj);
// => { Zone: [ { ExtWall: [ { ResWin: '10' } ], HVACDist: { HVACCool: [ { ResWin: '10' } ] } } ] }
</script>
Disclaimer: I'm the author of object-scan
The syntax is very similar to the JSONPath sytax, but it provides callbacks. The code could easily be generified to allow renaming of multiple keys in a single iteration.
Note that obj is modified in place. You could do a copy before modification, if that is not desired (e.g. using lodash cloneDeep)
If you want to replace all keys with same name you can iterate the entire tree.
If you want to rename by path, basically loop the path drilling down until you are at the requested key.
const obj = {
"Zone": [{
ExtWall: [{
Win: "10"
}],
HVACDist: {
HVACCool: [{
Win: "10"
}]
}
}]
};
var WinPath = [
['$', 'Zone', 0, 'ExtWall', 0, 'Win'],
['$', 'Zone', 0, 'HVACDist', 'HVACCool', 0, 'Win']
];
function rename_by_path(obj, arr_path, new_name) {
var pointer = obj;
var dollar = arr_path.shift();
var key = arr_path.shift();
while (arr_path.length) {
pointer = pointer[key]
key = arr_path.shift();
}
pointer[new_name] = pointer[key]
delete pointer[key]
}
function rename_all(obj, key_name, new_name) {
const iterate = (obj) => {
if (!obj) {
return;
}
Object.keys(obj).forEach(key => {
var value = obj[key]
if (typeof value === "object" && value !== null) {
iterate(value)
}
if (key == key_name) {
obj[new_name] = value;
delete obj[key];
}
})
}
iterate(obj)
}
function rename_by_paths(obj, arr_paths, new_name) {
arr_paths.forEach(arr_path => rename_by_path(obj, arr_path, new_name))
}
rename_by_paths(obj, WinPath, "ResWin")
console.log(obj)
rename_all(obj, "ResWin", "ResWin2");
console.log(obj)
.as-console-wrapper {
max-height: 100% !important;
}

JS Want to Iterate and set value of each key in a nested object to empty "", and create array of such objects

Base Object :
obj = {
"place": "{{base_gplaceId}}",
"feedInputs": [
{
"subCategoryQuestion": "{{base_gquestionId}}",
"context": "other",
"image": "abc.jpg",
"mediaMetadata": {
"stickerList": [
{
"id": "someid2",
"sticker": "delish",
"weight": 3
}
],
"textList": [
{
"text": "What an evening!!!"
}
]
}
}
]
};
more keys can have more nesting,
want to set the values of keys = "", one by one and push the updated object to an array
Expected OP :
[
{"place":"","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]}
,...........]
tried couple of recursions, but not able to break after update inside the nested objects,
any simplistic approach ?
You could iterate the properties and change the values who are not objects. For having access to the complete object store the root as well and take a copy of the object with stringify and parse for the result set.
function visitAll(object, root = object) {
return Object
.keys(object)
.flatMap(k => {
if (object[k] && typeof object[k] === 'object') return visitAll(object[k], root);
const value = object[k];
object[k] = '';
const result = JSON.parse(JSON.stringify(root));
object[k] = value;
return result;
});
}
var object = { place: "{{base_gplaceId}}", feedInputs: [{ subCategoryQuestion: "{{base_gquestionId}}", context: "other", image: "abc.jpg", mediaMetadata: { stickerList: [{ id: "someid2", sticker: "delish", weight: 3 }], textList: [{ text: "What an evening!!!" }] } }] },
result = visitAll(object);
result.forEach(o => console.log(JSON.stringify(o)));
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to transform array of strings into specific tree structure in Javascript

I get a list of file paths from the backend, it represents a folder structure and looks like this:
paths = ["path/to/file1.doc", "path/to/file2.doc", "foo/bar.doc]
The lengths of the paths are arbitrary. In order to use a file tree component (angular2-tree-component) I need to transform this data into the following format:
nodes = [
{
"name": "path",
"children": [
{
"name": "to",
"children": [
{"name": "file1.doc"},
{"name": "file2.doc"}
]
}
]
},
{
"name": "foo",
"children": [
{"name": "bar.doc"}
]
}
]
I think the most efficient way to transform the data is to
Map the array with the files mirroring a tree structure first and then to
Iterate over every key in order to finalise the "children/parent" relations.
Step one:
transformToTree(data) {
const tree = {};
function addPathsToTree(paths) {
let map = tree
paths.forEach(function(item) {
map[item] = map[item] || {};
map = map[item];
});
}
data.forEach(function(path) {
let pathPart = path.split('/');
addPathsToTree(pathPart);
});
return pathTree;
}
When passing "nodes" into the transformToTree function (transformToTree(nodes)), I get the following result:
{
"path": {
"to": {
"file1.doc": {},
"file2.doc": {}
}
},
"foo": {
"bar": {}
}
}
I don't know how to proceed from here = how to iterate over all the keys and values while building the final array in the required structure.
There are a few examples like this or that on SO, but I was not able to understand how I could adapt them to my needs.
I would go with two nested loops, one for pathes and one for the splitted names and find the name or create new objects.
var paths = ["path/to/file1.doc", "path/to/file2.doc", "foo/bar.doc"],
result = [];
paths.reduce((r, path) => {
path.split('/').reduce((o, name) => {
var temp = (o.children = o.children || []).find(q => q.name === name);
if (!temp) o.children.push(temp = { name });
return temp;
}, r);
return r;
}, { children: result });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Push to object instead of array

I'm using underscore to extract some props into a separate object but the structure is not as I want:
let element = {
foo: 0,
bar: 1,
baz: _.map(
_.filter(element.properties, (prop) =>
_.contains(validationProps, prop.name)), (rule) =>
({ [rule.name]: rule.value }) )
}
.. returns an array of objects for baz:
[ {"required":true} , {"maxLength":242} ]
.. what I need however is:
{ "required":true, "maxLength":242 }
Or use JavaScript's Array.prototype.reduce()
The reduce() method executes a reducer function (that you provide) on each member of the array resulting in a single output value.
let data = [{
"name": "label",
"value": "Short 2"
},
{
"name": "required",
"value": true
},
{
"name": "maxLength",
"value": 242
}
];
let reformatted = data.reduce((pv, cv) => {
pv[cv.name] = cv.value;
return pv;
}, {});
console.log(reformatted);

Merge arrays inside an array in javascript

Going through the link Merge/flatten an array of arrays in JavaScript? is somewhat what i need. But with that link and many other links shows merging of arrays of two arrays. What I have is as follows
[
{
"setter":[
{
"keyname":"Sample Size",
"cond":"=",
"value":1
}
]
},
{
"setter":[
{
"joinedcond":"and"
},
{
"keyname":"Sample Size",
"cond":"=",
"value":2
}
]
}
]
That is I have an array and inside that array I have an array "setter".
What I want is actually merging all setter array as a single array. Having said the merge should produce below output
[
{
"setter":[
{
"keyname":"Sample Size",
"cond":"=",
"value":1
},
{
"joinedcond":"and"
},
{
"keyname":"Sample Size",
"cond":"=",
"value":2
}
]
}
]
Help would be appreciated. Thanks
You can do it by using Array#reduce
var arr = [{"setter":[{"keyname":"Sample Size","cond":"=","value":1}]},{"setter":[{"joinedcond":"and"},{"keyname":"Sample Size","cond":"=","value":2}]}];
var finalArr = arr.reduce((a,x)=>{
(a[0].setter = a[0].setter || []).push(...x.setter);
return a;
},[{}]);
console.log(finalArr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use a hash table for the outer keys and concat the inner values with a dynamic approach for the outer keys, like setter.
var data = [{ setter: [{ keyname: "Sample Size", cond: "=", value: 1 }] }, { setter: [{ joinedcond: "and" }, { keyname: "Sample Size", cond: "=", value: 2 }] }],
result = data.reduce(function (hash) {
return function (r, o) {
Object.keys(o).forEach(function (k) {
if (!hash[k]) {
hash[k] = {};
r.push(hash[k]);
}
hash[k][k] = (hash[k][k] || []).concat(o[k]);
});
return r;
};
}(Object.create(null)), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using ES6 Rest and Spread operators we can achieve the same with a recursive function call:
let cdata= data.slice();//Copy the data
let condensedArray=[];
function flatten(data, ...rest){
let [{ setter }] = data;
condensedArray = [...condensedArray, ...setter];
data.splice(0,1);
data.length>0?flatten(data, ...data):console.log('Complete');
}
flatten(cdata, ...cdata);
console.log(condensedArray);

Categories

Resources