setState on nested map break origin object structure in ReactJS - javascript

I have an array of objects, and I want to add/change a new property given it matches type and key.
[
{
"type": "a",
"units": [
{
"key": "keyofba"
},
{
"key": "mytargetkey"
}
]
},
{
"type": "b",
"units": [
{
"key": "keyofb"
}
]
},
{
"type": "ab",
"units": [
{
"key": "mytargetkey"
}
]
}
]
I tried this
this.setState({
schema: schema.map(s => {
if (s.type === 'a' || s.type === 'ab') { //hardcord for testing
return s.units.map(unit => {
if (unit.key === 'mytargetkey') {
return {
...unit,
newProp: 'newProp value'
}
} else {
return { ...unit }
}
})
} else {
return { ...s }
}
})
But somehow it doesn't work, I think I missed something, need spotter.

That's because you have to return the list modified inside the new object, and if not it the target, return the element as is:
schema.map(s => {
if (s.type === 'a' || s.type === 'ab') { //hardcord for testing
return {...s, units: s.units.map(unit => {
if (unit.key === 'mytargetkey') {
return {
...unit,
newProp: 'newProp value'
}
} else {
return unit
}
})}
} else {
return s
}
})

Related

Restructure Nested JSON Data And Removing Some Keys

I need to restructure a nested JSON data.
Here is how it looks like:
{
"MainKey1": [
{
"Section1": {
"ParentTag1 Mapped Label": {
"ParentTag1": {
"Tag1 Mapped Label": {
"Tag1": "1234567890"
}
}
}
}
},
{
"Section2": {
"ParentTag1 Mapped Label": {
"ParentTag1": {
"Tag1 Label": {
"Tag1": "111222333444"
},
"Tag2 Label": {
"Tag2": "121212"
},
"Tag3 Label": {
"Tag3": "0987654321"
}
}
}
}
}
],
"MainKey2": [
{
"Section1": {
"ParentTag1 Mapped Label": {
"ParentTag1": {
"Tag1 Mapped Label": {
"Tag1": "1234567890"
}
}
}
}
}
]
}
And this is a sample of the converted JSON:
{
MainKey: [
{
Section1: [
{
ParentTag1: [
{ Tag1: "1234567890" }
]
}
]
},
{
Section2: [
{
ParentTag1: [
{ Tag1: "111222333444" },
{ Tag2: "121212" },
{ Tag3: "0987654321" }
]
}
]
}
],
MainKey2: [
{
Section1: [
{
ParentTag1 : [
{ Tag1: "1234567890" }
]
}
]
}
]
}
Rules:
Everything inside a MainKey (outermost keys, could be any name) should be an array
All labels should be stripped (as the label could be any name, without the actual word "Label", we can determine if it is a label based on the depth level. Since the JSON will have the label as the parent and the actual "tag" as a child.
Here is what I currently have (it is a mess, sorry!)
function convertJson (jsonObj) {
const mainKeys = Object.keys(jsonObj)
let output = {}
for (let i = 0; i < mainKeys.length; i++) {
const mainKey = mainKeys[i]
let result = []
output[mainKey] = result
for (let j = 0; j < jsonObj[mainKey].length; j++) {
const innerObj = {...jsonObj[mainKey][j]}
const sectionName = Object.keys(innerObj)[0]
const sectionObj = {}
sectionObj[sectionName] = []
const index = result.push(sectionObj) - 1
parseObj(innerObj[sectionName], result[index], 0) // if I change 2nd param to: result, it generates incorrect output
}
}
console.log(output)
}
function parseObj (innerObj, result, depthCount) {
for (var key in innerObj) {
if (typeof innerObj[key] === "object") {
if (depthCount % 2 === 1) {
const parentObj = {}
parentObj[key] = []
result.push(parentObj)
depthCount++
parseObj(innerObj[key], parentObj[key], depthCount)
} else {
depthCount++
parseObj(innerObj[key], result, depthCount)
}
} else {
const keyValuePairObj = {}
keyValuePairObj[key] = innerObj[key]
result.push(keyValuePairObj)
}
}
return result
}
convertJson(json)
But it generates an error:
Uncaught TypeError: result.push is not a function
Now if I change line 90 from:
parseObj(innerObj[sectionName], result[index], 0)
to:
parseObj(innerObj[sectionName], result, 0)
Here is incorrect output:
{
"MainKey1": [
{
"Section1": []
},
{
"ParentTag1": [
{
"Tag1": "1234567890"
}
]
},
{
"Section2": []
},
{
"ParentTag1": [
{
"Tag1": "111222333444"
},
{
"Tag2 Label": [
{
"Tag2": "121212"
}
]
},
{
"Tag3": "0987654321"
}
]
}
],
"MainKey2": [
{
"Section1": []
},
{
"Tag1": "1234567890"
}
]
}
And here is my fiddle:
https://jsfiddle.net/kzaiwo/L4avxmyd/36/
Thanks a lot! Appreciate any help!

How to create a nested data-structure where a value is supposed to become an array type in the presence of a certain substring value?

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; }

A function to obtain all leaf node properties of an object in an array of string in javascript

Request you to please help in building a function in javascript to obtain the mentioned output from the input given.
INPUT : An object (possibly a nested object)
example :
{
"message":"string" ,
"data1": {
"Output1": {
"leaf1": "abc",
"Leaf2": "123"
}
}
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
OUTPUT : An array of string
Example :
str= ["message", "data1.Output1.leaf1", "data1.Output1.leaf2" , "data2.Output2.leaf3","data2.Output2.leaf4"]
Something like this it will work
const getBranches = (data, prefix=[]) => {
if (typeof(data) !== 'object') {
return prefix.join('.')
}
return Object.entries(data).flatMap(([k, v]) => getBranches(v, [...prefix, k]))
}
const data = {
"message": "string",
"data1": {
"Output1": {
"leaf1": "abc",
"Leaf2": "123"
}
},
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
console.log(getBranches(data))
Second version
const data = {
"message": "string",
"data1": {
"Output1": {
"leaf1": [{
"b": {
"c": "12"
}
}]
}
},
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
const getBranches = (data, prefix = []) => {
if (typeof(data) !== 'object') {
return prefix.join('.')
}
return Object.entries(data).flatMap(([k, v]) => Array.isArray(data) ? getBranches(v, [...prefix]) : getBranches(v, [...prefix, k]))
}
console.log(getBranches(data))

How to convert JSON to list of key with dotnotation

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);

How to put all values into an array from a JSON object with specific key, Deep keys

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.

Categories