So I am given a json and I have to clean it up, by removing all the 'blank' fields. The fields that are considered blank are:
empty arrays or objects,
strings with whitespace ("" or " "),
null values.
This is what I have so far:
const isBlank = (val) => {
if(val === null ) {
return true
}
if(typeof val === 'string'){
return val.trim().length === 0;
}
return val.length === 0
};
function clean(jsonInput) {
Object.keys(jsonInput).forEach( key => {
if(typeof jsonInput[key] === 'object' && !isEmpty(jsonInput[key])){
clean(jsonInput[key])
}else {
isEmpty(jsonInput[key_field]) && delete jsonInput[key]
}
})
}
This is the jsonInput I am working with:
{
"print": "notBlank",
"this": "notBlank",
"example": "notBlank",
"blank": null,
"allBlanks": [
{
"from": "",
"blank0": null
}
],
"att2": {
"blank1": "",
"blank2": []
},
"att3": {
"one": "1",
"two": "2",
"three": "3",
"blank3": " "
}
}
This should be the output:
{
"print": "notBlank",
"this": "notBlank",
"example": "notBlank",
"att3": {
"one": "1",
"two": "2",
"three": "3"
}
}
Instead I am getting this:
{
"print": "notBlank",
"this": "notBlank",
"example": "notBlank",
"allBlanks": [{ }],
"att2": {},
"att3": {
"one": "1",
"two": "2",
"three": "3",
}
}
It seams like I am unable to remove the objects... Any ideas what I am doing wrong? Any way I can fix it.
Also is there a way to do this so that I don't change the original object and instead I make a duplicate, maybe with map or filter?
The main issue here is that [{}] was not defined as "empty" because it was an array of length 1 with an object in it. However, because you would like empty objects to be considered empty, and thus arrays with empty objects to be empty, you need to also recurse inside of your isEmpty function to cover these angles.
Note the two recursive calls for arrays and objects added to isEmpty.
As for as copying goes, the quick dirty way would be to first stringify then parse the json. You can see this towards the bottom of the code with the line
var jsonCopy = JSON.parse(JSON.stringify(json));
There are more complex ways of deep copying as well, please read What is the most efficient way to deep clone an object in JavaScript? for more information there.
const isEmpty = (val) => {
if(val === null ) {
return true
}
if(typeof val === 'string'){
return val.trim().length === 0;
}
if(val instanceof Array){
if( val.length === 0 ) return true;
return val.every( v =>
isEmpty(v)
);
}
if(val === Object(val)){
if(Object.keys(val).length == 0) return true;
return Object.values(val).every(
v => isEmpty(v)
);
}
return val.length === 0;
};
function clean(jsonInput) {
Object.keys(jsonInput).forEach( key => {
if(typeof jsonInput[key] === 'object' && !isEmpty(jsonInput[key])){
clean(jsonInput[key])
}else {
isEmpty(jsonInput[key]) && delete jsonInput[key]
}
})
}
var json = {
"print": "notBlank",
"this": "notBlank",
"example": "notBlank",
"blank": null,
"allBlanks": [
{
"from": "",
"blank0": null
}
],
"att2": {
"blank1": "",
"blank2": []
},
"att3": {
"one": "1",
"two": "2",
"three": "3",
"blank3": " "
}
};
var jsonCopy = JSON.parse(JSON.stringify(json));
clean(jsonCopy);
console.log(jsonCopy);
Some features like this already exist in the lodash
https://lodash.com/docs/4.17.10#omitBy
Avoid using delete in javascript, its slow and not a good pratice
Example:
var _ = require("lodash")
var oldObj = {a:1, b:2, c:null, d:"aa"}
var newObj = _.omitBy(oldObj, (value, key) =>
_.isNull(value) ||
(_.isString(value) && _.isEmpty(value)) ||
(_.isArray(value) && _.isEmpty(value)) );
console.log("Final", newObj) //Final { a: 1, b: 2, d: 'aa' }
isEmpty return true if value is a number https://lodash.com/docs/4.17.10#isEmpty
Edit:
The keyword delete throws an exception in strict mode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
Related
I have the following JSON.
var a = {
"0": "123",
"1": 'gio',
"again": "blabla",
"ah" : "fff",
"ss" : {
"0": "444",
"1": "555",
"final": "letssee"
}
}
I want to get the following one
var a = {
"again": "blabla",
"ah" : "fff",
"ss" : {
"final": "letssee"
}
}
The idea is that we recursively go through the whole object and remove all the keys that are numbers. Object could go deeper and deeper, so that's why we need recursion.
I tried to come up with a code, but somehow my solution is huge, and very ugly.
I'd appreciate a ES6 way(clean and easy)...
const isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
const removeNumericKeys = (o) =>
Object.fromEntries(
Object.entries(o).flatMap(([k, v]) =>
isNaN(Number(k)) ? [[k, isObj(v) ? removeNumericKeys(v) : v]] : []
)
);
basically you can loop using for ... in using recursion and check for the keys of the object using parseInt with isNaN to check if it is numeric or not.
this is a destructive way to achive it, if you look, it will change the principal object.
let obj = {
"0": "123",
"1": 'gio',
"again": "blabla",
"ah": "fff",
"ss": {
"0": "444",
"1": "555",
"final": "letssee"
}
}
function deleteRecursive(obj) {
for (let k in obj) {
const parsedKey = parseInt(k);
// if it is NOT NAN it means it is numeric, so we delete it
if (!isNaN(parsedKey)) {
delete obj[k];
} else if (typeof obj[k] == "object" && obj[k] !== null) {
deleteRecursive(obj[k]);
}
}
}
deleteRecursive(obj);
console.log(obj)
this is another way to do it if you want to keep the old object and assign it to a new one.
let obj = {
"0": "123",
"1": 'gio',
"again": "blabla",
"ah": "fff",
"ss": {
"0": "444",
"1": "555",
"final": "letssee"
}
}
let newObj = {}
function deleteRecursive(obj, newObj) {
for (let k in obj) {
const parsedKey = parseInt(k);
// if it is NOT NAN it means it is numeric, so we dont add it
if (typeof obj[k] == "object" && obj[k] !== null) {
newObj[k] = {}
deleteRecursive(obj[k], newObj[k]);
} else if (isNaN(parsedKey)) {
newObj[k] = obj[k]
}
}
}
deleteRecursive(obj, newObj);
console.log(newObj)
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 '.'
I'm trying to modify a string with Typescript. The string is created by the JSON.stringify() method.
I want to remove the properties "id", "lightStatus" and the "value" attributes of "inputPort" and "outputPort". (I only need their attribute "id".)
console.log(JSON.stringify(this.light));
// Results in -> {"id":1,"name":"Light Switch","lightStatus":true,"inputPort":{"id":2,"value":0},"outputPort":{"id":2,"value":false},"resistance":100}
I tried to do it the following way but it doesn't recognize "inputPort.id" and "outputPort.id". This is what I tried and what it resulted in.
var savedLight = JSON.stringify(this.light, ["name", "inputPort.id", "outputPort.id", "resistance"]);
// Results in -> {"name":"Light Switch","resistance":100}
The result should include the properties "name", "inputPort id", "outputPort id" and "resistance". Like this:
{"name":"Light Switch","inputPort": 2, "outputPort": 2, "resistance":100}
Can anyone help me how I can get rid of the unnecessary properties?
You can pass a "replacer" function that returns the exact value you want.
var data = {"id":1,"name":"Light Switch","lightStatus":true,"inputPort":{"id":2,"value":0},"outputPort":{"id":2,"value":false},"resistance":100};
var result = JSON.stringify(data, function(k, v) {
switch (k) {
case "": case "name": case "resistance":
return v
case "inputPort": case "outputPort":
return v.id
default:
return undefined;
}
}, 2)
document.querySelector("pre").textContent = result
<pre></pre>
The "" represents the top level object. For that, "name", and "resistance", it simply returns the original value.
For "inputPort" and "outputPort" it returns the id property.
Anything else gets undefined, which means it gets omitted from the result.
You can use a replacer function for this.
var obj = {
"id": 1,
"name": "Light Switch",
"lightStatus": true,
"inputPort": {
"id": 2,
"value": 0
},
"outputPort": {
"id": 2,
"value": false
},
"resistance": 100
};
var stringified = JSON.stringify(obj, function(key, val) {
if (key === 'id' || key === 'lightStatus') {
return void(0);
}
if (key === 'inputPort' || key === 'outputPort') {
return val.id;
}
return val;
});
console.log(stringified);
You can apply Replacer function of JSON.stringify
var data='{"id":1,"name":"Light Switch","lightStatus":true,"inputPort":{"id":2,"value":0},"outputPort":{"id":2,"value":false},"resistance":100}';
var json=JSON.parse(data);
function replacer(i, val) {
switch (i) {
case "": case "name": case "resistance":
return val
case "inputPort": case "outputPort":
return val.id
default:
return undefined;
}
}
console.log(JSON.stringify(json,replacer));
I am trying to merge my objects and get a result like below
{
"sports": {
"basketball": "kobe",
"swimming": {
},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
Somewhere I am doing wrong in my logic can you help me out
but if I alternate my sportA and sportsB value I am getting expected
results...not sure what problem in my current scenario
providing my code below.
fiddle
https://jsfiddle.net/tjLk0frq/3/
var sportsA ={
"sports": {
"basketball": "kobe",
"football": "ronaldo"
}
};
var sportsB ={
"sports": {
"basketball": "",
"swimming": {
},
"football": "",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
};
function merge(sportsA, sportsB) {
for( var p in sportsB )
if( sportsA.hasOwnProperty(p) )
sportsA[p] = typeof sportsB[p] === 'object' ? merge(sportsA[p], sportsB[p]) : sportsB[p];
return sportsA;
}
merge(sportsA, sportsB );
console.log("unexpected result" + sportsA );
console.log( sportsA );
//expected
/*
{
"sports": {
"basketball": "kobe",
"swimming": {
},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
*/
You can use jQuery's extend method, with deep merge enabled, to do this:
var output = $.extend(true, sportsB, sportsA)
outputs:
{
"sports": {
"basketball": "kobe",
"swimming": {},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
You have mistake when you check sportsA.hasOwnProperty(p) in your case you only update properties that are in sportsA, but not add new from sportsB.
Also if sportsB[p] has falsy value you don't want to update it for that I've used (sportsB[p] || sportsA[p]).
Check this code.
var sportsA ={
"sports": {
"basketball": "kobe",
"football": "ronaldo"
}
};
var sportsB ={
"sports": {
"basketball": "",
"swimming": {
},
"football": "",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
};
function merge(sportsA, sportsB) {
for( var p in sportsB )
if( sportsA.hasOwnProperty(p) ) {
sportsA[p] = typeof sportsB[p] === 'object' ? merge(sportsA[p], sportsB[p]) : (sportsB[p] || sportsA[p]);
} else {
sportsA[p] = sportsB[p];
}
return sportsA;
}
merge(sportsA, sportsB );
console.log("unexpected result" + sportsA );
console.log( sportsA );
Here you go (pure JS):
function merge(obj1, obj2) {
var result = {};
for (var prop in obj1) {
if (typeof obj1[prop] === "object" && typeof obj2[prop] === "object")
result[prop] = merge(obj1[prop], obj2[prop]);
else
result[prop] = obj1[prop];
}
for (var prop in obj2) {
result[prop] = (result[prop]? result[prop]: obj2[prop]);
}
return result;
}
console.log(merge(sportsA, sportsB));
This returns a new object, rather than modify an existing one, however.
In the first for..in loop, we check if we need to recurse first, otherwise set the property of result.
In the second for..in loop, we check if the property was already defined or if it's empty, and set the property accordingly.
Output:
{
"sports": {
"basketball": "kobe",
"football": "ronaldo",
"swimming": {},
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
JSFiddle demo
The logic is breaking because when you only loop the property keys in one of the objects, you won't see the property keys that only exist in the other object.
You can get the root level keys of an object using Object.keys() which returns an array of the property names. Then you can merge the 2 sets of keys at same level and know all the final output properties needed
Then iterate those to get final results
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