I was trying to normalize a very deeply nested JSON which contains all possible ways JSON can be created. A part of JSON can be seen in below code snippet.
What is my end goal
I am converting the nested JSON into a simple JS object like below
{
key1: value,
key2: value,
...
}
Problem i faced with the below solution is that when it comes to Objects with values as array
i failed to find a way to see its key values.
if you run below code
key4,key5, key6 wont get displayed with the console.log only its value gets printed.
key1 -- values
key2 -- values
key3 -- value3
0 --
0 -- some_value
Code snippet
const req = {
request: {
results: {
key1: 'values',
results: [
{
key2: 'values',
},
],
},
params: {
key3: 'value3',
query: {
key4: [''],
key5: ['123456'],
key6: ['some_value'],
},
},
},
};
function normaliseJSON(obj) {
for (let k in obj) {
if (obj[k] instanceof Object) {
normaliseJSON(obj[k]);
} else {
console.log(`${k} -- ${obj[k]}`);
}
}
}
normaliseJSON(req);
Is there any way to get the keys of key4,5,6 ?
also open to any other solution to normalise such JSON
The reason your recursion goes inside the array is since ['123456'] instanceof Object is true in javascript (typeof(['asd']) also gives "object"). To check if something is an array have to check with Array.isArray(something)
In template literals when you try to embed an array eg ...${['123456']} in the end it will show as ...123456 without the brackets. Therefore in situation of Arrays need to JSON.stringify(arr)
There may be better ways of doing this but I created a function called arrayHasObject which checks if an array has object elements. This was to catch the inner results array and ignore key4,key5 and key6.
The recursion will happen if obj[k] is an object and not an array or if obj[k] is an array and it has an object element.
Since recursion is hard to visualize I recommend https://pythontutor.com/ . It is mostly for Python but works for JS as well. It can help you visualize these things and to find where things go wrong
Ofcourse the way I have written it will break if something like key4: [{a:'abc'}] since arrayHasObject gives true for this. Maybe will need to change the function accordingly.
function arrayHasObject(arr) {
return arr.some((x) => typeof(x)==='object' && !Array.isArray(x))
}
const req = {
request: {
results: {
key1: 'values',
results: [
{
key2: 'values',
},
],
},
params: {
key3: 'value3',
query: {
key4: [''],
key5: ['123456'],
key6: ['some_value'],
},
},
},
};
function normaliseJSON(obj) {
for (let k in obj) {
if ((obj[k] instanceof Object && !Array.isArray(obj[k])) || (Array.isArray(obj[k]) && arrayHasObject(obj[k]))) {
normaliseJSON(obj[k]);
} else {
if (Array.isArray(obj[k])){
console.log(`${k} -- ${JSON.stringify(obj[k])}`);
}
else{
console.log(`${k} -- ${obj[k]}`);
}
}
}
}
normaliseJSON(req);
Related
Here is my issue:
I have a JSON Object coming in a response from an http request, meaning not all objects inside the jsonResponse will be typeOf string, nor will I know how many objects are found inside the jsonResponse.
let jsonResponse = {
prop1: {
prop: 'objectKey1'
},
prop2: {
prop2: 'objectKey2',
num: 2,
stringy: 'stringy'
},
key: 'string',
anotherKey: 'anotherString',
prop3: {
objectKey3: 3,
objectKey4: 'string',
prop4: {
moreKeys: 'string',
num1: 666
}
},
property: 'anotherString2'
}
I want to go over each property/value inside this object, including all of it's child object keys and values in order to check if they are type of string.
if they are a type of string I want to send them to another function to perform a certain logic to that string, and to change it's value inside the jsonResponse.
I tried to do something like this -
Object.keys(jsonResponse).forEach((key, indexValue)=>{
if(typeof(jsonResponse[key])==='string'){
somelogic(jsonResponse[key])
} else if (typeof(jsonResponse[key] === 'object')){
//recurssive function to keep getting keys and values inside the jsonResponse
}
})
It seems that I'm missing some objects inside the jsonResponse because of that, and I don't know how to iterate through the child objects and their respected keys and values.
I know i'm not going through all objects, I'm just unsure how to do it recurssivly or if recurssive is the right way to do it.
I also tried using Object.entries(), but the tricky part for this is that I'll never know how many objects inside the jsonResponse are there, nor will I have any idea how many object of objects are there.
what is the fastest way to do it in manner of time-complexity?
let jsonResponse = {
prop1: {
prop: 'objectKey1'
},
prop2: {
prop2: 'objectKey2',
num: 2,
stringy: 'stringy'
},
key: 'string',
anotherKey: 'anotherString',
prop3: {
objectKey3: 3,
objectKey4: 'string',
prop4: {
moreKeys: 'string',
num1: 666
}
},
property: 'anotherString2'
}
function somelogic(a) {
console.log('>>>', a);
}
function getStringKeys(jsonResponse) {
Object.keys(jsonResponse).forEach((key, indexValue)=>{
if(typeof(jsonResponse[key])==='string'){
somelogic(jsonResponse[key])
} else if (typeof(jsonResponse[key] === 'object')){
getStringKeys(jsonResponse[key]);
}
})
}
getStringKeys(jsonResponse);
I have a JSON object that will sometimes be an object (a single instance) and sometimes be an array (multiple instances of the object). I need to write an if statement that basically says if this section of JSON is an object, wrap it in an array containing that single object, and if it's an array containing multiple objects, return that array. In either instance I'm returning an array containing either 1 or multiple objects.
Here is what the JSON looks like when it is NOT an array.
"link": {
"values": {
"key1": "value1",
...
"key8": "value8"
},
"key9": "value9"
}
And it should look like this when it's an array:
"link": [{
"values": {
"key1": "value1",
...
"key8": "value8",
},
"key9": "value9"
}]
EDIT -----------------------------
This is what I've written so far that is producing the type error I'm experiencing.
const isLinkArray = sections.values;
isLinkArray.link = Array.isArray(isLinkArray.link) ? isLinkArray.link : [isLinkArray.link];
EDIT 2 ---------------------------
The final answer ended up being almost identical to Kinglish' answer, so I figured I would post it here. The issue I ran into was that the JSON right above 'link' was also an array and that was causing the typescript error.
const sectionsWithLinkArray = sections.map((section) => {
return {
values: {
...section.values,
link: !Array.isArray(section.values.link) ? [section.values.link] : section.values.link,
},
};
});
You can use Array.isArray to check, then convert
let data = {
"link": {
"values": {
"key1": "value1",
"key8": "value8"
},
"key9": "value9"
}
}
data.link = Array.isArray(data.link) ? data.link : [data.link];
console.log(data)
This can be done by writing a simple function that checks if it's an array.
const returnArray = (value) => {
if (Array.isArray(value) {
return value;
}
return
}
updated the answer of #Kinglish to typescript one because you cannot change types of defined value as it giving error for this either simply ignore the typescript or define types that simply accept link in object and array of object or just created new variable that expect a link in the array and wrap it inside data by simply doing this:
const data = {
link: {
values: {
key1: 'value1',
key8: 'value8',
},
key9: 'value9',
},
};
// This is the type of data you can't change it by default and it doesn't expect array of object of `link`.
// const data: {
// link: {
// values: {
// key1: string;
// key8: string;
// };
// key9: string;
// };
// };
const linkArray = { link: Array.isArray(data.link) ? data.link : [data.link] };
// Now this is the type of linkArray that expect array of object of `link`
// const linkArray: {
// link: {
// values: {
// key1: string;
// key8: string;
// };
// key9: string;
// }[];
// };
console.log('data', data);
console.log('linkArray', linkArray);
Let's say I have this object:
a = {
key1: {
name: 'a',
import: 1234.7896,
discount: 122.34553
}
key2: {
name: 'b'
import: 8976.09998,
discount: 12.890999
}
}
and I have a function which format the number in some way and returning a string. Applying that function I want an object like this:
result = {
key1: {
name: 'a'
import: '1,234.78'
discount: '122.33'
}
key2: {
name: 'b'
import: '8,976.09'
discount: '12.89'
}
}
So, my goal is to take all the properties which contains numbers and format them.
How can I iterate the object to edit just the properties which contains a number and return another object with the same key and different numeric properties?
The natural approach to this task is recursion. Below I've assumed you have a format function that formats a number into a string as you desire, and defined recurseFormat to recurse through object values, looking for values of type "number" to run format on. You might need to modify the typeof value === 'object' test to suite your needs for when to recurse.
function format(number) {
...
}
function recurseFormat(object) {
for (const [key, value] of Object.entries(object)) {
if (typeof value === 'number') {
object[key] = format(value)
} else if (typeof value === 'object') {
recurseFormat(value);
}
}
}
Note that this code modifies your object in place. If you want to deep copy your object at the same time, that can be done with a similar recursive approach; let me know and I can try writing that.
You can implement recursion which will work for nested object as well. Something like this:
const convertToString=obj=>{
return Object.fromEntries(Object.entries(obj).map(([k,v])=>[k, typeof v=="object" ? convertToString(v) : v.toLocaleString()]))
};
const a = { key1: { name: 'a', import: 1234.7896, discount: 122.34553 }, key2: { name: 'b', import: 8976.09998, discount: 12.890999 } };
console.log(convertToString(a));
Is possible dynamically add properties to nested object in Typescript ? Because my object is dynamic. In one case i have obj.
[
{someObject},
{
prop1:1,
columns: [
components: [
columns: [
components:[
{
type: number,
key: 'key1'
},
{
type: textfield,
key: 'key2'
}
]
]
]
]
}
]
And for example for object with key key2 i need add some flag. And in another case i get object less nested. Does exists any for example lodash function for this operation, or i have to iterate this object by recursion ?
You will have to recursively search. Thankfully this is not too difficult to implement:
const findAndUpdate = ($obj, $key, $val) => {
Object.keys($obj).includes($key) ?
$obj[$key] = $val
: Object.values($obj).forEach($nestedObj => findAndUpdate($nestedObj, $key, $val));
return $obj;
}
I used Object.<method> instead of lodash methods so that this can be easily replicated accross enviroments. This function will take in an initial object, the key you want to update and the value that you want to update it to.
Usage:
const foo = {
b: {
c: {
d: 5
}
}
}
findAndUpdate(foo, "d", 56);
console.log(foo) // -> { b: { c: { d: 56 } } } }
It checks if the key exists in the current layer of the object. If it doesn't then we call the function again for each object in the current layer - passing in the original object as a reference. If it does find a key in the current layer, then it will update the object that the reference points to. Eventually after the stack is cleared then we return our original updated object. Obviously if no keys are found that match the target key originally passed in then the object will remain unchanged.
If you want more customisation you could change the $val to take in a function $func instead:
const findAndUpdate = ($obj, $key, $func) => {
Object.keys($obj).includes($key) ?
$obj[$key] = $func($obj[$key])
: Object.values($obj).forEach($nestedObj => findAndUpdate($nestedObj, $key, $val));
return $obj;
}
Then you can now do something like this:
findAndUpdate(foo, "d", old => old+1 );
console.log(foo) // -> { b: { c: { d: 6 } } } }
So, I'm trying out some prototyping, and have had success implementing forEach on the prototype to handle its array of objects. The arrow function callback works fine, and I thought the same thing might work for .reduce(), however, as you can see, the notation which works for a normal Array does not work for my ArraySet prototype. On a side note, the same function in arrow notation does not work.
What's missing in my understanding of what's happening here so that I can fix this and move on?
function ArraySet(items) {
// this._items = []
this._items = items
}
ArraySet.prototype.forEach = function forEach(cb) {
return this._items.forEach(cb);
}
ArraySet.prototype.reduce = function reduce(cb) {
return this._items.reduce(cb);
}
let arr = new ArraySet([{
key1: 'property2',
key3: 'propertyx'
},
{
key1: 'property4',
key3: 'propertyy'
},
{
key1: 'climate change',
key3: 'propertyx'
},
{
key1: 'climate change',
key3: 'propertyx'
},
])
arr.forEach(el => {
console.log(el)
});
x = arr.reduce(function (map, obj) {
if (obj.key3 === 'propertyx'){
map.push(obj.key1)
}
return map
}, []) //<-- final argument is the instantiating literal of the reigning map type: [], {}, ''
EDIT:
Thanks to Maheer Ali's answer detailing the use of the spread operator (...) the problem was easily solved. Maheer excellently expands on other functions where the same approach will apply.
Digging into the why, I learned before the spread operactor came along .apply() was customarily used in function calls to ensure all required arguments were available in the execution. The spread operator has evolved from applicability to arrays (like a list of arguments) since it was introduced to also include objects. It can also copy an array, replacing arr.splice().
Here's an adaptation of one of the examples on the MDN:
function myFunction(v, w, x, y, ...z) {
console.log(v + ' ' + w + ' ' + x + ' ' + y + ' ' + z)
}
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3, 8]);
More available in the reference material: Spread Syntax
The reduce() have two parameters one is the callback and second is initial value of accumulator. So you need to use rest parameters for the methods and then pass all parameters to reduce()
Note: In reduce() you usually pass second argument. In forEach(),map() there is also a second optional parameter. That parameter will be this binded to callback passed to particular method.If you need to use that make sure to do the same as reduce()
See the below working snippet
function ArraySet(items) {
// this._items = []
this._items = items
}
ArraySet.prototype.forEach = function forEach(cb) {
return this._items.forEach(cb);
}
ArraySet.prototype.reduce = function reduce(...args) {
return this._items.reduce(...args);
}
let arr = new ArraySet([{
key1: 'property2',
key3: 'propertyx'
},
{
key1: 'property4',
key3: 'propertyy'
},
{
key1: 'climate change',
key3: 'propertyx'
},
{
key1: 'climate change',
key3: 'propertyx'
},
])
arr.forEach(el => {
console.log(el)
});
x = arr.reduce((map, obj) => {
if (obj.key3 === 'propertyx'){
map.push(obj.key1)
}
return map
}, []) //<-- final argument is the instantiating literal of the reigning map type: [], {}, ''
console.log(x)
On a side note, the same function in arrow notation does not work
The arrow function doesn't have their own this bindings. They use this of parent scope. As you are are using this in your methods so you can't use Arrow functions. Prototype methods can't be ever written with arrow functions.