Why lodash converts my array into object? - javascript

I am new to lodash, and created a function that removes the key from the object where a value is null or blank.
But when i am passing my object which contains some part as array it removes array and converts it into an object.
Below is my code which i tried:
_.mixin({ 'removeFalsies': this.removeFalsies });
_.mixin({
removeEmptyObjects: this.removeEmptyObjects
});
removeFalsies (obj) {
return _.transform(obj, function (o, v, k) {
if (v && typeof v === 'object') {
if (v !== '') {
o[k] = _.removeFalsies(v);
}
} else if (v === false) {
o[k] = v;
} else if (v) {
o[k] = v;
}
});
}
removeEmptyObjects (obj) {
return _(obj)
.pickBy(_.isObject)
.mapValues(_.removeEmptyObjects)
.omitBy(_.isEmpty)
.assign(_.omitBy(obj, _.isObject))
.value();
}
Below is the JSON which i am providing to omit blank and null value, so it should remove all the properties from "smallMap" object as series, align are blank object and should remove height as well.
On the other hand, it should remove "mid" from heatrules as well as series1 from children array.
var finalProp = {
"smallMap": {
"series": {},
"align": {},
"height": ""
},
"series": [
{
"heatRules": [
{
"min": "#555",
"mid": null
}
],
"mapPolygons": {
"tooltipText": "{name}",
"togglable": true
},
"id": "value"
}
],
"children": [
{
"type": "HeatLegend",
"width": "100%",
"series1": null
}
]
}
_.removeEmptyObjects(_.removeFalsies(finalProp));
It is removing everything as expected but only 1 issue it is converting array to object.
It is providing below incorrect output instead of series: {0 : {}} it should provide series: [{}] and instead of children: {0 :{}} it should provide [{}].
{
"series": {
"0": {
"heatRules": {
"0": {
"min": "#555"
}
},
"mapPolygons": {
"tooltipText": "{name}",
"togglable": true
},
"id": "value"
}
},
"children": {
"0": {
"type": "HeatLegend",
"width": "100%"
}
}
}
i am not able to find where is the issue any help is appreciated.

main two problems one which was mention by #Akrion for that you can do something as below:
removeFalsies (obj) {
return _.transform(obj, function (o, v, k, l) {
// here you have to check for array also as typeof will return object for array also
if (v && _.isObject(v) && !_.isArray(v)) {
if (v !== '' && !_.omitBy(v, _.isObject)) {
o[k] = removeFalsies(v);
}
} else if(_.isArray(v)) {
if(!o.hasOwnProperty(k)) o[k] = [];
//And if it is array loop through it
_.forEach(v, function(v1, k1) {
o[k].push(removeFalsies(v1));
});
} else if (v === false) {
o[k] = v;
} else if (v) {
o[k] = v;
}
});
}
while calling function just try to remove mapValues as it create object and convert array in objects because of that your key 0 is getting converted to key
removeEmptyObjects =function (obj) {
return _(obj).pickBy(_.isObject).omitBy(_.isEmpty).assign(_.omitBy(obj, _.isObject)).value();
}
Hope this will help you, not tested properly yes just short description.

You issue is in your function removeFalsies where you do not account properly for arrays. Remember Arrays are also objects and your check for if (v && typeof v === 'object') is not sufficient.
What happens is that you do:
if (v && typeof v === 'object') {
if (v !== '') {
o[k] = _.removeFalsies(v);
}
}
Where when you pass an array in that _.transform the k is 0 and you end up creating an object with key 0 which value is then _.removeFalsies(v);
Just put a breakpoint or debugger in that if and you can easily see the issue.
Also note that lodash already has plenty of methods to check for objects like _.isObject as well as arrays _.isArray ... _.isString & _.isEmpty etc.

If your code uses
typeof v === 'object'
it will return true for arrays.
To check for array, use
Array.isArray(t)
Treating array as object and iterating over keys will result in your issue.
Sample Function To Recurse But Not Process Arrays
function removeFalsies(obj) {
return _.transform(obj, function(o, v, k, l) {
if (Array.isArray(obj) {
for (let arrItem of obj) {
removeFalsies(arrItem);
}
return
}
// else not array...
})
}

Related

Cannot convert undefined or null to object when iterating through valid JSON

I have valid JSON with this structure
const myJSONExample = {
"SubItems": [
{
"SubItems": [
{
"ItemNo": "000001"
}
],
"ItemNo": null,
"Number": null,
"price": 114.46
},
{
"SubItems": [
{
"Group": "0.4.004"
}
],
"type": null
},
{
"SubItems": [
{
"ItemNo": "000005"
},
{
"Quantity": 2
}
],
"Material": "Steel"
},
{
"Description": null
}
]
}
and just simply trying to format all number types in it, using recursive iteration.
const iterate = (obj) => {
Object.keys(obj).forEach(key => {
if(typeof(item[key]) == "number"){
item[key] = new Intl.NumberFormat("de-DE").format(item[key]) //format number for german lang.
}
if (typeof obj[key] === 'object') {
iterate(obj[key])
}
})
}
iterate(myJSONExample);
I used this functions on other JSONs, and been trying to understand for some time, why this throws TypeError: Cannot convert undefined or null to object
null is an "object" hence your issue. So add a truthy check
const iterate = (obj) => {
Object.keys(obj).forEach(key => {
const value = obj[key]
const valueType = typeof value
if (valueType === "number") {
obj[key] = new Intl.NumberFormat("de-DE").format(value)
} else if (valueType === 'object' && value) {
iterate(value)
}
})
}

How to parse true and false strings in an array to become booleans

How can I parse the true and false strings in an array to become boolean in Javascript?
For instance,
from:
{"id":1,"dashboardId":1,"w":2,"h":2,"x":0,"y":0,"i":"n0","minW":1,"minH":1,"maxH":1000,"moved":"false","static":"false","widget":"Photo"}
to:
{"id":1,"dashboardId":1,"w":2,"h":2,"x":0,"y":0,"i":"n0","minW":1,"minH":1,"maxH":1000,"moved":false,"static":false,"widget":"Photo"}
The values from moved and static have to be a boolean but they appear as a string. Is there a way to only change those values?
This is the function where I fetch the arrays:
loadData = () => {
let dashboardId = 1;
return axios
.get('api/dashboards/' + dashboardId)
.then(result => {
//#TODO Parse true and false strings to become booleans
console.log(result);
this.setState({
items: result.data,
selectedOption: '',
newCounter: originalLayouts.length
});
})
.catch(error => {
console.log(JSON.stringify(this.state.items));
console.error('error: ', error);
})
};
parse the true and false strings in an array to become boolean
Strings in an array, you say? Iterate using Array.map()
const items = ["true", "false", "something else"]
const booleans = items.map(boolFromStringOtherwiseNull)
console.log({items, booleans}) // show result
function boolFromStringOtherwiseNull(s) {
if (s == 'true') return true
if (s == 'false') return false
return null
}
Objects? Iterate using Object.values()
const data = {"id":1,"dashboardId":1,"w":2,"h":2,"x":0,"y":0,"i":"n0","minW":1,"minH":1,"maxH":1000,"moved":"false","static":"false","widget":"Photo"};
const booleans = Object.values(data).map(boolFromStringOtherwiseNull); // convert
console.log({data, booleans}); // show result
function boolFromStringOtherwiseNull(s) {
if (s == 'true') return true
if (s == 'false') return false
return null
}
Convert only boolean strings, and maintain the original Object's structure?
const data = {"id":1,"dashboardId":1,"w":2,"h":2,"x":0,"y":0,"i":"n0","minW":1,"minH":1,"maxH":1000,"moved":"false","static":"false","widget":"Photo"}
const result = Object.entries(data)
.map(boolFromStringOtherwiseNoOp) // convert 'boolean strings' to boolean
.reduce(gatherObjectFromEntries, {}) // collect all entries into 1 object
console.log({data, result}); // result is an object where non-boolean-strings are untouched.
function boolFromStringOtherwiseNoOp([key, value]) {
if (value == 'true') return [key, true]
if (value == 'false') return [key, false]
return [key, value]
}
function gatherObjectFromEntries(accumulation, [key, value]) {
accumulation[key] = value
return accumulation
}
Hope this helps. Cheers,
You can iterate the object, and check its value strictly for "false" or "true" and parse them manually to boolean.
E.g.
function mapObjectValues (obj = {}) {
for (var key in obj) {
var val = obj[ key ];
if (val === "false") {
obj[ key ] = false;
}
else if (val === "true") {
obj[ key ] = true;
}
}
}
var tmp = {
"id": 1,
"dashboardId": 1,
"w": 2,
"h": 2,
"x": 0,
"y": 0,
"i": "n0",
"minW": 1,
"minH": 1,
"maxH": 1000,
"moved": "false",
"static": "false",
"widget": "Photo"
};
mapObjectValues(tmp);
console.log(tmp);
You can loop over the object keys and then compare those boolean strings and assign the actual Boolean value to that property:
var data = {"id":1,"dashboardId":1,"w":2,"h":2,"x":0,"y":0,"i":"n0","minW":1,"minH":1,"maxH":1000,"moved":"false","static":"false","widget":"Photo"};
Object.keys(data).forEach((key) => {
if(data[key] === 'false'){
data[key] = false;
}
if(data[key] === 'true'){
data[key] = true;
}
});
console.log(data);
You can do like this:
var data = { "id": 1, "dashboardId": 1, "w": 2, "h": 2, "x": 0, "y": 0, "i": "n0", "minW": 1, "minH": 1, "maxH": 1000, "moved": "false", "static": "false", "widget": "Photo" };
Object.keys(data).forEach((key) => {
if (data[key] === 'false' || data[key] === 'true') {
data[key] = JSON.parse(data[key]);
}
});
console.log(data);
`
for(key in a){
a[key] == "true" ? a[key] = true : a[key] == "false" ? a[key] = false : '';
}
You can simply loop over the object and use the ternary operator, to make it concise,
where a is your original object

What is the fastest way of finding if object has a value (multidimensional object)

I have an multi-dimensional object that has a 3 level depth. I am trying to if the 3rd-level object has a given value. What comes to my mind is for-looping through each level and checking with (Object.values(obj).indexOf('red') > -1) but as far as I understand, for looping is a slow way.
For example, in the below object, what is the fastest way to check if any of the most inner values have red value or not, returning a boolean?
myObj: {
user1: {
apples: {
1: "red",
2: "green",
3: "black"
},
cherry: {
2: "green"
4: "dark"
}
},
user2: {
orange: {
1: "orange"
}
}
}
This is a recursive method that uses Oject.values() and Array.some() to check if a value exists in an object:
const obj = {"user1":{"apples":{"1":"red","2":"green","3":"black"},"cherry":{"2":"green","4":"dark"}},"user2":{"orange":{"1":"orange"}}};
const findValue = (o, val) => Object.values(o)
.some((v) => v && typeof(v) === 'object' ? findValue(v, val) : (v === val));
console.log(findValue(obj, 'red'));
console.log(findValue(obj, 'gold'));
You could use a depth-first search and look for nested objects.
function contains(object, value) {
return Object.values(object).some(
v => v && typeof v === 'object'
? contains(v, value) :
v === value
);
}
var myObj = { user1: { apples: { 1: "red", 2: "green", 3: "black" }, cherry: { 2: "green", 4: "dark" } }, user2: { orange: { 1: "orange" } } };
console.log(contains(myObj, 'red'));
console.log(contains(myObj, 42));
An other solution could be to use a stack an perform a linear search without a recusion.
This works as breadth-first-search.
function contains(object, value) {
var stack = Object.values(object),
v;
while (stack.length) {
v = stack.shift();
if (v && typeof v === 'object') {
stack.push(...Object.values(v));
continue;
}
if (v === value) {
return true;
}
}
return false;
}
var myObj = { user1: { apples: { 1: "red", 2: "green", 3: "black" }, cherry: { 2: "green", 4: "dark" } }, user2: { orange: { 1: "orange" } } };
console.log(contains(myObj, 'red'));
console.log(contains(myObj, 42));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use a recursive depth-first function to iterate through the keys, and then at the deepest level, just return true. One "gotcha" is to make sure that strings don't return single character strings when iterating since that will just recurse infinitely.
function hasKey(object, depth = 0) {
if (depth === 0) {
return true;
}
for (const key in Object(object)) {
const value = object[key];
// prevent nested checks of characters in strings
if (typeof value !== 'string' || value.length !== 1 || typeof object !== 'string') {
if (hasKey(value, depth - 1)) {
return true;
}
}
}
return false;
}
let myObj = {"user1":{"apples":{"1":"red","2":"green","3":"black"},"cherry":{"2":"green","4":"dark"}},"user2":{"orange":{"1":"orange"}}};
// has keys at depth 3
console.log(hasKey(myObj, 3));
// does not have keys at depth 4
console.log(hasKey(myObj, 4));
While this answer may be longer in line-count, it truly iterates the keys at each depth rather than buffering all the Object.values() into an array at each depth, which technically disqualifies the other answers from being able to claim a "depth-first" approach, since the buffering causes "breadth-first" behavior.

Recursively collect values for property using lodash

For a nested complex object or array, I would like to collect all values for a given property name. Example:
var structure = {
name: 'alpha',
array: [
{ name: 'beta' },
{ name: 'gamma' }
],
object: {
name: 'delta',
array: [
{ name: 'epsilon' }
]
}
};
// expected result: [ 'alpha', 'beta', 'gamma', 'delta', 'epsilon' ]
It's obvious how to achieve this using plain JS, but: Is there any elegant, concise approach using lodash?
[edit] Current variant below. Nicer solutions welcome!
function getPropertyRecursive(obj, property) {
var values = [];
_.each(obj, function(value, key) {
if (key === property) {
values.push(value);
} else if (_.isObject(value)) {
values = values.concat(getPropertyRecursive(value, property));
}
});
return values;
}
This can be done elegantly with the following mixin, which is a recursive version of _.toPairs:
_.mixin({
toPairsDeep: obj => _.flatMap(
_.toPairs(obj), ([k, v]) =>
_.isObjectLike(v) ? _.toPairsDeep(v) : [[k, v]])
});
then to get the result you want:
result = _(structure)
.toPairsDeep()
.map(1)
.value()
If there are scalar properties other than name, you'll have to filter them out:
result = _(structure)
.toPairsDeep()
.filter(([k, v]) => k === 'name')
.map(1)
.value()
There's no Lodash/Underscore function that I know if that will do what you're looking for.
So what are you looking to do? Well, specifically you're looking to extract the values of all of the name properties out of a aggregate structure. How would we generalize that? In other words, if you were looking to add such functionality to Lodash/Underscore, how would you reframe the problem? After all, most people don't want to get the values of the name properties. You could create a generic function where you supply the name of the property you want, but...thinking even more abstractly than that, what you really want to do is visit all of the nodes in a aggregate structure and do something with them. If we consider aggregate structures in JavaScript as generic trees, we can take a recursive approach using a depth-first walk:
function walk(o, f) {
f(o);
if(typeof o !== 'object') return;
if(Array.isArray(o))
return o.forEach(e => walk(e, f));
for(let prop in o) walk(o[prop], f);
}
Now we can do what you're looking for by walking the structure and adding things to an array:
const arr = [];
walk(structure, x => if(x !== undefined && x.name) arr.push(x.name));
This isn't quite functional enough for my tastes, though...there's a side effect on arr here. So an even better generic approach (IMO) would be to allow a context object to ride along (or an accumulator if you will, a la Array#reduce):
function walk(o, f, context) {
f(o, context);
if(typeof o !== 'object') return context;
if(Array.isArray(o)) return o.forEach(e => walk(e, f, context)), context;
for(let prop in o) walk(o[prop], f, context);
return context;
}
Now you can call it like this, side-effect free:
const arr = walk(structure, (x, context) => {
if(x !== undefined && x.name) context.push(x.name);
}, []);
Iterate the object recursively using _.reduce():
function getPropertyRecursive(obj, prop) {
return _.reduce(obj, function(result, value, key) {
if (key === prop) {
result.push(value);
} else if (_.isObjectLike(value)) {
return result.concat(getPropertyRecursive(value, prop));
}
return result;
}, []);
}
var structure = {
name: 'alpha',
array: [{
name: 'beta'
}, {
name: 'gamma'
}],
object: {
name: 'delta',
array: [{
name: 'epsilon'
}]
}
};
var result = getPropertyRecursive(structure, 'name');
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>
You could iterate the object and call it again for arrays or objects. Then get the wanted property.
'use strict';
function getProperty(object, key) {
function iter(a) {
var item = this ? this[a] : a;
if (this && a === key) {
return result.push(item);
}
if (Array.isArray(item)) {
return item.forEach(iter);
}
if (item !== null && typeof item === 'object') {
return Object.keys(item).forEach(iter, item);
}
}
var result = [];
Object.keys(object).forEach(iter, object);
return result;
}
var structure = { name: 'alpha', array: [{ name: 'beta' }, { name: 'gamma' }], object: { name: 'delta', array: [{ name: 'epsilon' }] } };
console.log(getProperty(structure,'name'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Based on the answer ( https://stackoverflow.com/a/39822193/3443096 ) , here's another idea for mixin:
_.mixin({
extractLeaves: (obj, filter, subnode, subpathKey, rootPath, pathSeparator) => {
var filterKv = _(filter).toPairs().flatMap().value()
var arr = _.isArray(obj) ? obj : [obj]
return _.flatMap(arr, (v, k) => {
if (v[filterKv[0]] === filterKv[1]) {
var vClone = _.clone(v)
delete vClone[subnode]
vClone._absolutePath = rootPath + pathSeparator + vClone[subpathKey]
return vClone
} else {
var newRootPath = rootPath
if (_.isArray(obj)) {
newRootPath = rootPath + pathSeparator + v[subpathKey]
}
return _.extractLeaves(
v[subnode], filter, subnode,
subpathKey, newRootPath, pathSeparator
)
}
})
}
});
This work for this example JSON, where you want to extract leaf-nodes:
{
"name": "raka",
"type": "dir",
"children": [{
"name": "riki",
"type": "dir",
"children": [{
"name": "roko",
"type": "file"
}]
}]
}
Use it this way:
_.extractLeaves(result, {type: "file"}, "children", "name", "/myHome/raka", "/")
And you will get:
[
{
"name": "roko",
"type": "file",
"_absolutePath": "/myHome/raka/riki/roko"
}
]

How to access json data property in nodejs

I have a json that is similar to the code below.
var jsonData = {
"Config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"CpuShares": 0,
"Cpuset": "",
"Domainname": "",
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOME=/root"
]
"Hostname": "git",
"WorkingDir": ""
},
"Created": "2015-03-03T08:59:05.735601013Z",
"Name": "/git",
"NetworkSettings": {
"Ports": {
"22/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "2008"
}
],
"80/tcp": null,
"8006/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "9008"
}
]
}
},
"ResolvConfPath": "/etc/resolv.conf",
"State": {
"Pid": 6146,
"Running": true,
"StartedAt": "2015-03-03T08:59:05.829535361Z"
}
}
As you can see any properties has it's own unique name. My question is: How can I access the properties like Env in this way getValue(jsonData, 'Env'); ?
My json is much bigger and more complex than what I put above.
Here's a function that allows you to recurse through an object to find the information you need if you don't know the structure of the object. Note that this will break out of the function when the first instance of that key has been found. This means that if you have more than one key called HostIp, it will only find the first one.
function getValue(obj, key) {
var found = null;
var recurse = function (obj, key) {
for (var p in obj) {
if (p === key) {
found = obj[p];
break;
}
if (obj[p] !== null && typeof obj[p] === 'object') recurse(obj[p], key);
}
}
recurse(obj, key);
return found;
}
getValue(jsonData, 'Env'); // [ "PATH=/usr/local/sbin:/usr/local/bin…", "HOME=/root" ]
If you want to find all instances of a particular key name use the following code instead. It will add all matches to an array and return that array once the object has been trawled. This isn't particularly useful tho because it doesn't provide the context in which it found the key, but it might give you a few ideas.
function getValue(obj, key) {
var found = [];
var recurse = function (obj, key) {
for (var p in obj) {
if (p === key) {
found.push(obj[p]);
}
if (obj[p] !== null && typeof obj[p] === 'object') recurse(obj[p], key);
}
}
recurse(obj, key);
return found;
}
getValue(jsonData, 'HostPort'); // [ "2008", "9008" ]
DEMO

Categories