I have a dynamic JSON structure ex:
{
"video": {
"width": 1920,
"height": 1080,
"video_codec": "H264",
"CBR": "4337025",
"frame_rate": {
"numerator": 25,
"denominator": 1
},
"specified": {
"numerator": 1,
"denominator": 1
},
"gop": {
"length": 50,
"reference_frames": 3,
"sub_gop": "StaticType"
},
"codec_details": {
"profile": "Main",
"level": "Level4",
"entropy_encoding": "CABAC",
"video_output": "AVC1"
}
}
}
I want to transform it to tree node
export class TrackDetailsNode {
key: string;
value: string;
children?: TrackDetailsNode[];
}
Output Example:
{
"key": "video",
"children": [
{
"key": "width",
"value": "1920"
},
{
"key": "frameRate",
"children": [
{
"key": "numerator",
"value": "60"
},
{
"key": "denominator",
"value": "1"
}
]
}
]
}
I need it in a recursive way. I want to build a tree from the provided JSON.
Tried multiple solution but is taking a long time to parse.
var obj = {
"video": {
"width": 1920,
"height": 1080,
"video_codec": "H264",
"CBR": "4337025",
"frame_rate": {
"numerator": 25,
"denominator": 1
},
"specified": {
"numerator": 1,
"denominator": 1
},
"gop": {
"length": 50,
"reference_frames": 3,
"sub_gop": "StaticType"
},
"codec_details": {
"profile": "Main",
"level": "Level4",
"entropy_encoding": "CABAC",
"video_output": "AVC1"
}
}
};
function transform(data, output, children) {
Object.keys(data).forEach((key) => {
let value = data[key];
if (children) {
if (typeof value === 'object') {
const child = [];
children.push({
'key': key,
children: child
});
transform(value, undefined, child)
} else {
children.push({
'key': key,
value
})
}
} else {
if (typeof value === 'object') {
output.key = key;
output.children = [];
transform(value, undefined, output.children);
} else {
output.key = key;
output.value = value;
}
}
});
return output;
}
console.log(transform(obj, {}));
const INPUT = {
"video": {
"width": 1920,
"height": 1080,
"video_codec": "H264",
"CBR": "4337025",
"frame_rate": {
"numerator": 25,
"denominator": 1
},
"specified": {
"numerator": 1,
"denominator": 1
},
"gop": {
"length": 50,
"reference_frames": 3,
"sub_gop": "StaticType"
},
"codec_details": {
"profile": "Main",
"level": "Level4",
"entropy_encoding": "CABAC",
"video_output": "AVC1"
}
}
};
const isObject = (obj) => obj !== null && typeof obj === 'object';
const Transform = (input, isChildren) => {
const output = isChildren ? [] : {};
if (input && isObject(input)) {
Object.keys(input).forEach(key => {
const value = input[key];
const val = isObject(value) ? Transform(value, true) : value;
const valueKey = isObject(value) ? "children" : "value";
if (isChildren) {
output.push({
"key": key,
[valueKey]: val
});
} else {
output.key = key;
output[valueKey] = val;
}
})
}
return output;
}
const result = Transform(INPUT);
console.log(JSON.stringify(result, null, 2));
Related
I have input data like this:
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}]
I am trying to achieve an output like this based on substring '[index]' (i.e. if that substring is not present then that element should be an object instead of an array):
{
"outField2": "something",
"outField3[index]": [{
"outField4": "something",
"outField5": "something",
"outField6": {
"outField7": "something"
}
}]
}
My current code (below) is able to produce the outField3 as an object if there is no substring '[index]' but I'm unable to find a good solution to generate it as an array in the presence of the substring. Can someone help out? I've tried a few options but none gives me the desired result.
function doThis(item, index) {
let path = map[index].name.split(".");
if (path.length > 1) {
createNestedObject(mapOutput, path, map[index].value);
} else {
mapOutput[map[index].name] = map[index].value;
};
};
function createNestedObject(element, path, value) {
var lastElement = arguments.length === 3 ? path.pop() : false;
for (var i = 0; i < path.length; i++) {
if (path[i].includes('[index]')) {
/*some logic here to push the child elements
that do not contain [index] as an array into
the ones that contain [index]*/
} else {
element = element[path[i]] = element[path[i]] || {};
};
}
if (lastElement) element = element[lastElement] = value;
return element;
};
const map = [{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}];
let mapOutput = {};
map.forEach(doThis);
let mapOutputJSON = JSON.stringify(mapOutput, null, 2);
console.log(mapOutputJSON);
.as-console-wrapper { min-height: 100%!important; top: 0; }
you can do something like this
const data = [{
"name": "outField2",
"value": "something"
},
{
"name": "outField3[index].outField4",
"value": "something"
},
{
"name": "outField3[index].outField5",
"value": "something"
},
{
"name": "outField3[index].outField6.outField7",
"value": "something"
}
]
const buildObject = (paths, value, obj) => {
if (paths.length === 0) {
return value
}
const [path, ...rest] = paths
if(path.includes('[index]')) {
return {
...obj,
[path]: [buildObject(rest, value, (obj[path] || [])[0] || {})]
}
}
return {
...obj,
[path]: buildObject(rest, value, obj[path] || {})
}
}
const result = data.reduce((res, {
name,
value
}) => buildObject(name.split('.'), value, res), {})
console.log(result)
A possible generic approach which in my opinion also assigns the correct type of the OP's "outField3[index]" property (object type instead of an Array instance) is based on reduce where ...
the outer loop iterates the array of { name, value } items
by executing a single function accumulateObjectTypeFromPathAndValue where ...
this function does split each name-value into an array of object-path keys which then gets iterated by the inner reduce method where the passed object programmatically accumulates nested key-value pairs.
function accumulateObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
}
return obj[key];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The above implementation of the 2nd reducer function then could be changed according to the OP's custom array-type requirements ...
function accumulateCustomObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
if (key.endsWith('[index]')) {
obj[ key ] = [obj[ key ]];
}
}
return Array.isArray(obj[ key ])
//? obj[ key ].at(-1) // last item.
? obj[ key ][obj[ key ].length - 1] // last item.
: obj[ key ];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateCustomObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have below 3 objects listed below with data.
I want to find const oldData.data.parameter_value = b and oldData.data.label_value = b inside
each object of const newData.selected_parameter_value.
And if value exist in newData array, then replace all newData.selected_parameter_value.parameter_value = b and newData.selected_parameter_value.label_value = b , with const updatedData.data.parameter_value = red and const updatedData.data.label_value = red.
For ex -
const oldData = {
"action": "edit",
"data": {
"parameter_value": "b",
"label_value": "b"
}
};
const updatedData = {
"action": "update",
"data": {
"parameter_value": "red",
"label_value": "red"
}
};
const newData = [
{
"context": [],
"selected_parameter_value": [
{
"label_value": "a",
"parameter_value": "a"
},
{
"label_value": "b",
"parameter_value": "b"
}
]
},
{
"context": [],
"selected_parameter_value": [
{
"label_value": "b",
"parameter_value": "b"
}
]
}
];
Expected Output
const newData = [
{
"context": [],
"selected_parameter_value": [
{
"label_value": "a",
"parameter_value": "a"
},
{
"label_value": "red",
"parameter_value": "red"
}
]
},
{
"context": [],
"selected_parameter_value": [
{
"label_value": "red",
"parameter_value": "red"
}
]
}
];
Can Anyone help me to do this.
i tried below code but i dont know how to update matching objects in newData and keep all objects as it is.
editContextData(data) {
if (data.action === 'edit') {
this.oldData = data;
}
if (data.action === 'update') {
const oldData = this.oldData;
const updatedData = this.newData;
const newData = this.selectedParameterContext.records;
const newSelectedParameterContext = {
'records': []
};
newData.forEach(function (record) {
const newSelectedParameterValues = [];
record.selected_parameter_value.forEach(function (parameter) {
if(parameter.parameter_value === oldData.data.parameter_value && parameter.label_value === oldData.data.label_value){
// here i want to update object and push new values in array.
newSelectedParameterValues.push(updatedData);
}
});
newSelectedParameterContext.records.push({ 'selected_parameter_value': newSelectedParameterValues });
});
console.log(newSelectedParameterContext,"rrr");
this.selectedParameterContext = newSelectedParameterContext;
}
}
newData.forEach((item) => {
item.selected_parameter_value.forEach((obj) => {
if (oldData.data.parameter_value === obj.parameter_value) {
obj.parameter_value = updatedData.data.parameter_value;
}
if (oldData.data.label_value === obj.label_value) {
obj.label_value = updatedData.data.label_value;
}
})
Or if both have to be the same at the same moment then:
newData.forEach((item) => {
item.selected_parameter_value.forEach((obj) => {
if (oldData.data.parameter_value === obj.parameter_value && oldData.data.label_value === obj.label_value) {
obj.parameter_value = updatedData.data.parameter_value;
obj.label_value = updatedData.data.label_value;
}
});
});
I have a search input in my angular application that should compare the input data with different object properties
<div class="forms">
<div class="search-wrapper">
<input
class="search"
[ngClass]="{'searching': searching}"
type="text"
(input)="changeSearch($event.target.value)"
/>
<label class="">
<span>Rechercher</span>
</label>
</div>
</div>
the logic I use is as follows
public changeSearch(searchTerm: string) {
this.searching = !!searchTerm;
if (!this.searching) {
this.orders.forEach(order => {
order.show = true;
});
return;
}
const extendSearchIn = ['order_number', 'transaction.item.product.name'];
this.orders.forEach(order => {
order.show = true;
extendSearchIn.forEach(property => {
this.searchByProperty(order, property, searchTerm);
});
});
}
public searchByProperty(order, property, searchTerm) {
const prop = this.getSearchProperty(order, property);
if (prop === undefined) { return false; }
return (<String>prop.toLowerCase()).startsWith(searchTerm.toLowerCase());
}
public getSearchProperty(item: object, property: string) {
let itemCopy = Object.assign({}, item);
let result: any;
const props = property.split('.');
props.forEach(prop => {
if (itemCopy !== undefined) {
itemCopy = itemCopy[prop];
}
});
result = itemCopy !== undefined ? itemCopy : result;
return result;
}
and the structure of each object 'order' is like the following
{
"functional_id": "202006101058160012400000SD4AYAA1",
"transactions": [
{
"quantity": 2,
"price": 140,
"item": {
"name": "Carton",
"description": "+ 2 recharges",
"product": {
"name": "Coffret rouge"
}
},
"amount": 280
},
{
"quantity": 2,
"price": 140,
"item": {
"name": "10 coffrets",
"description": "+ 2 recharges",
"product": {
"name": "Coffret gris"
}
},
"amount": 280
},
{
"quantity": 2,
"price": 60,
"item": {
"name": "PACK N°1 comprenant :",
"description": "6 modèles",
"product": {
"name": "AfuBOX",
"description": "60,8 x 39,5 x 16,5 cm"
}
},
"amount": 120
}
],
"show": true,
"date": "10/06/2020",
"order_number": "105816",
"overallAmount": 680
}
you would need to set the 'show' property to false so that those that don't comply with what was entered in the search field would be hidden
Someone to make me see where my mistake is.
Thank you in advance
I have simplified the logic with a forEach and checking if the value I receive from the input contains any of the search criteria I wanted to apply.
I hope that this will help you to explain if you find yourself in a similar situation.
public changeSearch(searchTerm: string) {
this.searching = !!searchTerm;
if (!this.searching) {
this.orders.forEach(order => {
order.show = true;
});
return;
}
this.orders.forEach(order => {
order.show = true;
this.searchByProperty(order, searchTerm);
});
}
public searchByProperty(order, searchTerm) {
const id = (order.order_number + '');
const amount = (order.overallAmount + '');
order.transactions.forEach(items => {
const title = items.item.product.name.toLowerCase();
if (title.includes(searchTerm) || id.includes(searchTerm) || amount.includes(searchTerm)) {
order.show = true;
} else {
order.show = false;
}
});
}
I am converting JSON keys to the list with dot-notation. If any dot is there represent nested jsonobject and if any [](array notation) is there resents jsonarray.
var keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if (Array.isArray(obj[el])) {
return [...res, ...keyify(obj[el][0], prefix + el + '[].')];
} else if (typeof obj[el] === 'object' && obj[el] !== null) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
} else {
return [...res, prefix + el];
}
}, []);
Above is the sample code that I am using for the converion. If input is
{
"input": {
"test": {
"phone": [
{
"phone1": "123"
}
]
}
},
"customer": [
{
"lastname": "def",
"firstname": "abc"
}
]
}
Output will be:
[ 'input.test.phone[].phone1',
'customer[].lastname',
'customer[].firstname' ]
But the above code searches for only first JSONObject's keys in the JSONArray. But if the input is like this:
{
"input": {
"test": {
"phone": [
{
"phone1": "123"
},
{
"a": "456"
}
]
}
},
"customer": [
{
"lastname": "def",
"firstname": "abc"
}
]
}
Then in the above JSON case the code will give output :
[ 'input.test.phone[].phone1',
'customer[].lastname',
'customer[].firstname' ]
So, the key a is missing only phone1 is coming in the list.So, how to get if multiple json keys are there then get keys with index of first occurence.
Expected output
[ 'input.test.phone[0].phone1',
'input.test.phone[1].a',
'customer[0].lastname',
'customer[0].firstname' ]
And if the JSONarray is value then it should be replaced by empty string.
For input:
const data = {
"input": {
"test": {
"phone": [
{
"phone1": ["123456"]
},
{
"a": ["1","2","3","4"]
}
]
}
},
"customer": [
{
"lastname": "def",
"firstname": "abc"
}
]
}
In this case "phone1": ["123456"] and "a": ["1","2","3","4"] are Json array as values this case lis will be like:
Expected Output:
[ 'input.test.phone[0].phone1',//instead of 'input.test.phone[0].phone1[0]'
'input.test.phone[1].a',//instead of 'input.test.phone[1].a[0]','input.test.phone[1].a[1]','input.test.phone[1].a[2]','input.test.phone[1].a[3]',
'customer[0].lastname',
'customer[0].firstname' ]
In the above case jsonarray should be considered as value not key.
You could use for...in loop to create recursive function for this and check if the current data input is an array or not to add dot or square brackets.
const data = { "input": { "test": { "phone": [ { "phone1": ["123456"] }, { "a": ["1","2","3","4"] } ] } }, "customer": [ { "lastname": "def", "firstname": "abc" } ] }
function parse(data, prev = '') {
const result = []
const check = data => {
if (typeof data !== 'object') {
return false
}
if (Array.isArray(data)) {
if (data.some(e => (typeof e != 'object'))) {
return false
}
}
return true;
}
for (let i in data) {
let dot = prev ? '.' : ''
let str = Array.isArray(data) ? `[${i}]` : dot + i
let key = prev + str;
if (check(data[i])) {
result.push(...parse(data[i], key))
} else {
result.push(key)
}
}
return result
}
const result = parse(data);
console.log(result)
You can traverse through the scope of the object and capture any paths that have a non-object value.
This is an extremely uncoupled and generic soulution.
const traverse = (obj, visitorFn, scope = []) => {
for (let key in obj) {
visitorFn.apply(this, [key, obj[key], scope]);
if (obj[key] !== null && typeof obj[key] === 'object') {
traverse(obj[key], visitorFn, scope.concat(key));
}
}
}
const scopeToPath = (obj) => obj.reduce((path, key) =>
path + (!isNaN(key) ? `[${key}]` : `.${key}`), '').substring(1);
const findObjectPaths = (obj) => {
let paths = [];
traverse(obj, (key, value, scope) => {
if (typeof value !== 'object') {
paths.push(scopeToPath(scope.concat(key)));
}
});
return paths;
};
console.log(findObjectPaths(getData()));
function getData() {
return {
"input": {
"test": {
"phone": [{ "phone1": "123" }, { "a": "456" }]
}
},
"customer": [{ "lastname": "def", "firstname": "abc" }]
};
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
You could take a nested approach by having a look to the types of the object.
function flat(object, keys = '') {
if (!object || typeof object !== 'object') return [keys];
if (Array.isArray(object))
return object.every(o => !o|| typeof o!== 'object')
? [keys]
: object.flatMap((o, i, { length }) =>
flat(o, `${keys}[${length === 1 ? '' : i}]`));
return Object
.entries(object)
.flatMap(([k, v]) => flat(v, `${keys}${keys && '.'}${k}`));
}
var data = { input: { test: { phone: [{ phone1: ["123456"] }, { a: ["1", "2", "3", "4"] }] } }, customer: [{ lastname: "def", firstname: "abc" }] },
result = flat(data);
console.log(result);
I am trying to parse a large json object with mostly useless information. I'm trying to access all '_text' keys and their values and put them in an array.
EX:
const myObj = {
"_declaration": {
"_attributes": {
"version": "1.0",
"encoding": "UTF-8",
"standalone": "yes"
}
},
"w:document": {
"_attributes": {
"xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
"xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex",
"xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
"xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
"xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
"xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
"xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
"xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
"xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
"xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
"xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink",
"xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d",
"xmlns:o": "urn:schemas-microsoft-com:office:office",
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
"xmlns:v": "urn:schemas-microsoft-com:vml",
"xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
"xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xmlns:w10": "urn:schemas-microsoft-com:office:word",
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
"xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid",
"xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex",
"xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
"xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
"xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml",
"xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
"mc:Ignorable": "w14 w15 w16se w16cid wp14"
},
"w:body": {
"w:p": [
{
"_attributes": {
"w14:paraId": "53160B82",
"w14:textId": "77777777",
"w:rsidR": "00484DC0",
"w:rsidRDefault": "00484DC0",
"w:rsidP": "00264A78"
},
"w:pPr": {
"w:spacing": {
"_attributes": {
"w:after": "0",
"w:line": "240",
"w:lineRule": "auto"
}
},
"w:rPr": {
"w:sz": {
"_attributes": {
"w:val": "36"
}
},
"w:szCs": {
"_attributes": {
"w:val": "36"
}
}
}
}
},
{
"_attributes": {
"w14:paraId": "0FC460F5",
"w14:textId": "77777777",
"w:rsidR": "00880E84",
"w:rsidRDefault": "00880E84",
"w:rsidP": "00264A78"
},
"w:pPr": {
"w:spacing": {
"_attributes": {
"w:after": "0",
"w:line": "240",
"w:lineRule": "auto"
}
},
"w:rPr": {
"w:sz": {
"_attributes": {
"w:val": "36"
}
},
"w:szCs": {
"_attributes": {
"w:val": "36"
}
}
}
},
"w:r": {
"w:rPr": {
"w:sz": {
"_attributes": {
"w:val": "36"
}
},
"w:szCs": {
"_attributes": {
"w:val": "36"
}
}
},
"w:t": {
"_text": "Teaching Guide/Lesson Template"
}
}
}
...More of the object, this is the JSON format that I
then parse using JSON.parse in my code to read
const myArray = /*An array*/
//myArray should be ["Some text", "that I want", "in an array"]
The order does matter I tried this with a recursive function but the object is too large and I'm exceeding the maximum call stack. Is there any function or library out there that I can use for this functionality?
Make a recursive function with Object.keys like so:
function getText(obj) {
let text = [];
Object.keys(obj).forEach(key => {
if (key == "_text") {
text.push(obj[key]);
}
if (typeof obj[key] == "object") {
text.push(getText(obj[key]));
}
});
const flatten = function(arr, result = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, result);
} else {
result.push(value);
}
}
return result;
};
return flatten(text);
}
const myObj = {
object1: {
deeperLevel: {
_text: "Some text"
}
},
object2: {
_text: "that I want"
},
object3: {
deeperLevel: {
EvenDeeper: {
_text: "in an array"
}
}
}
};
const myArr = getText(myObj);
console.log(myArr);
Flattening algorithm from this answer.