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
Related
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)
}
})
}
I have an object like the following :
[
{
"uid": "aaa-aaa",
"name": "foo",
"children": []
},
{
"uid": "aaa-bbb",
"name": "bar",
"children": [
{
"uid": "aaa-bbc",
"name": "baz",
"children": []
},
{
"uid": "aaa-ccc",
"name": "fooz",
"children": [
{
"uid": "aaa-bcb",
"name": "Yeah !",
"children": []
}
]
}
]
}
]
I am trying to write a function that would take that object an uid as parameters and would return a path to the element with the uid in that object (or null if it's not found).
Something like this :
> getElementPath(bigObject, 'aaa-bcb')
[1, "children", 1, "children", 0]
or
> getElementPath(bigObject, 'aaa-bcb')
[1, 1, 0]
I know the function has to be recursive since there should be no limit in nesting levels. I have tried this but it always returns null :
function getElementPath (haystack, uid, currentPath = []) {
if (haystack.uid === uid) {
return currentPath
}
if (Array.isArray(haystack.children)) {
for (let i = 0; i < haystack.children.length; i++) {
let newPath = [...currentPath, i]
let path = getElementPath(haystack.children[i], uid, newPath)
if (path !== null) {
return path
}
}
}
return null
}
I'd use flat
Flatten the object and then loop over the Object keys until you find the one that has the appropriate value. Once you find it, the key is the path.
https://www.npmjs.com/package/flat
My (naive and quick) implementation would look like this. But what I don't love about it is that it knows to look at the "children" property, it's fine if you're data structure is well defined and doesn't change very often, the flat idea will work no matter if you change your data structure or not.
getPathForUid = (uid,obj,thisPath = []) => {
if(Array.isArray(obj)) {
return obj.reduce((acc,item,idx) => getPathForUid(uid,item,thisPath.concat(idx)),[]);
}
return obj.uid === uid ? thisPath : getPathForUid(uid,obj.children,thisPath.concat('children'));
}
Try this:
function getObject(listaJson, uid) {
var object = null,
param,
type = null;
if (listaJson.uid === uid) {
return listaJson;
}
for (param in listaJson) {
type = typeof(listaJson[param]);
if (type.toString().toLowerCase() === 'object') {
object = getObject(listaJson[param], uid);
}
if (object) {
return object;
}
}
return object;
}
console.log(getObject(json, 'aaa-aaa'));
console.log(getObject(json, 'aaa-bbb'));
console.log(getObject(json, 'aaa-bbc'));
console.log(getObject(json, 'aaa-ccc'));
console.log(getObject(json, 'aaa-bcb'));
console.log(getObject(json, 'aaa-xxx')); // null
console.log(getObject(json, 'yyy-jjj')); // null
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...
})
}
I have been reading through many of the great code examples which test for the existence of object key in a object with arrays. These are great...
My problem is the JSON returned has key value that must be used to get to the items inside the array. Here is an example. Look at "orders":
{"Routes": [
{
"route": {
"id": "1daf1f53-80b6-49d6-847a-0ee8b814e784-20180821"
},
"vehicle": {
"id": "1daf1f53-80b6-49d6-847a-0ee8b814e784"
},
"driver": {
"id": "6c2823be-374e-49e5-9d99-2c3f586fc093"
},
"orders": {
"6df85e5f-c8bc-4290-a544-03d7895526b9": {
"id": "6df85e5f-c8bc-4290-a544-03d7895526b9",
"delivery": {
"customFields": {
"custom": "5379"
}
},
"isService": true
}
}
}
]
};
The code I am using works up to the point where I have to specify the key value:
function checkProperty(obj, prop) {
var parts = prop.split('.');
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
if (obj !== null && typeof obj === "object" && part in obj) {
obj = obj[part];
} else {
return false;
}
return true;
}
Here are some samples that work and fail:
console.log(checkProperty(test, 'Routes.0.orders')); //Works returns true
console.log(checkProperty(test, 'Routes.0.orders.id')); //Fails returns false
console.log(checkProperty(test, 'Routes.0.orders.6df85e5f-c8bc-4290-a544-03d7895526b9.id)); //Fails returns false
I am at my wits end and would appreciate any help...
"orders": {
"6df85e5f-c8bc-4290-a544-03d7895526b9": {
"id": "6df85e5f-c8bc-4290-a544-03d7895526b9"
second test:
Id is not a direct child of "Orders" in your example:
orders.6df85e5f-c8bc-4290-a544-03d7895526b9.id
third test:
syntax error in the third - missing '.'
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"
}
]