I am attempting to abuse a reviver function with JSON.parse.
I basically want to make certain fields "null".
If I do this:
var json_data = JSON.parse(j, function(key, value) {
if (key == "name") {
return value;
} else {
return null;
}
});
The entire json_data object ends up null. In fact, no matter what I make the else, that defines the value of the json_object.
Interestingly, this works as expected:
var json_data = JSON.parse(j, function(key, value) {
if (key == "name") {
return "name";
} else {
return value;
}
});
The property "name" now has a value of "name".
JSON in question:
var j = '{"uuid":"62cfb2ec-9e43-11e1-abf2-70cd60fffe0e","count":1,"name":"Marvin","date":"2012-05-13T14:06:45+10:00"}';
Update
I just realized that the inverse of what I want to do works as well so I can nullify the name field:
var json_data = JSON.parse(j, function(key, value) {
if (key == "name") {
return null;
} else {
return value;
}
});
Through some experimentation, it looks like a final call is made to the function where the key is an empty string and the value is the top-level object:
> JSON.parse('{"hello": "world"}', function(k, v) { console.log(arguments); return v; })
["hello", "world"]
["", Object]
So you could use:
var json_data = JSON.parse(j, function(key, value) {
if (key == "name" || key === "") {
return value;
} else {
return null;
}
});
Now, since "" does appear to be a valid JSON key, to be 100% correct it might be better to use something like:
var json_data;
JSON.parse(j, function(key, value) {
if (key == "name") {
return value;
} else if (key === "") {
json_data = value;
return null;
} else {
return null;
}
});
But that might be a little bit paranoid ;)
It has a rather interesting behavior that the entire object is included in the objects passed to the reviver.
When the entire object is passed, the key is null.
http://jsfiddle.net/sGYGM/7/
var j = '{"uuid":"62cfb2ec-9e43-11e1-abf2-70cd60fffe0e","count":1,"name":"Marvin","date":"2012-05-13T14:06:45+10:00"}';
var json_data = JSON.parse(j, function(k, v) {
if (k === "" || k == "name") {
return v;
} else {
return null;
}
});
console.log(json_data);
As per https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/JSON/parse
The reviver is ultimately called with the empty string and the topmost value to permit transformation of the topmost value. Be certain to handle this case properly, usually by returning the provided value, or JSON.parse will return undefined.
Related
Suppose I have an object with depth-N like:
food = {
'Non-Animal': {
'Plants' : {
'Vegetables': {
...
}
},
'Minerals' : {
...
}
},
'Animal': {
...
}
}
And I want to add in this object the category 'Fruits', but I have to search the object where 'Plants' are and then add it. So I don't want to do in one statement:
food['Non-Animal']['Plants']['Fruits'] = {};
Since I want to search first where it belongs.
How can I add the fruits category to the object while iterating through it? What I have so far is:
addCategory(food, category, parent);
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name) {
obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
addCategory(p, category, parent);
} else {
}
}
}
How can I fix this routine to do this or is there a better way to do this?
If I'm understanding you correctly, I think you'd want your function to define a variadic parameter that takes individual names of the path you wish to traverse and create if necessary.
Using .reduce() for this makes it pretty easy.
const food = {
'Non-Animal': {
'Plants': {
'Vegetables': {}
},
'Minerals': {}
},
'Animal': {}
}
console.log(addCategory(food, "Non-Animal", "Plants", "Fruits"));
console.log(addCategory(food, "Non-Animal", "Minerals", "Gold"));
function addCategory(obj, ...path) {
return path.reduce((curr, name) => {
if (!curr) return null;
if (!curr[name]) return (curr[name] = {});
return curr[name];
// More terse but perhaps less readable
// return curr ? curr[name] ? curr[name] : (curr[name]={}) : null;
}, obj);
}
console.log(JSON.stringify(food, null, 2));
Looks fine. However you might want to terminate after adding the prop:
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name){
return obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
if(addCategory(p, category, parent)) return true;
}
}
}
I see only one mistake: The recursive call of addCategory cannot find the parent-variable, cause it's called parent_name in your scope.
var food = {
'Non-Animal': {
'Plants' : {
'Vegetables': {
}
},
'Minerals' : {}
},
'Animal': {}
}
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name){
obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
addCategory(p, category, parent_name);
} else {
}
}
}
console.log(food);
addCategory(food, 'Fruits', 'Plants');
console.log(food);
You can use reduce to create function that will take key as string and object as value that you want to assign to some nested object.
var food = {"Non-Animal":{"Plants":{"Vegetables":{}},"Minerals":{}},"Animal":{}}
function add(key, value, object) {
key.split('.').reduce(function(r, e, i, arr) {
if(r[e] && i == arr.length - 1) Object.assign(r[e], value);
return r[e]
}, object)
}
add('Non-Animal.Plants', {'Fruits': {}}, food)
console.log(food)
i have nested json data. i used the blow function.
var jsonSource={"error_code":0, "ext_info":{"name":{"firstName":"John","lastName":"Jonson","nickName":"JJ"}}};
var obj=JSON.parse(jsonSource),returnValue;
function showJson(obj){
for(var key in obj){
if(typeof obj[key]==='object'){
returnValue+='<div>'+key+'/\n';
showJson(obj[key]);
returnValue+='</div>';
} else{
returnValue+=key+'equal'+obj[key];
}
}
docoument.getElementById('data').innerHTML=returnValue;
}
as i said before , i have a large nested json data and when i parse it to showJson function ,it just shows one level of json data and puts others deep level of dataJson undefined.
what should i do to resolve the problem?
Recursive approach works more intuitively when done with actual return values. Have a look at https://jsfiddle.net/ughnjfh0/1/
var jsonSource='{"error_code":0, "ext_info":{"name":{"firstName":"John","lastName":"Jonson","nickName":"JJ"}}}';
var obj=JSON.parse(jsonSource);
function showJson(obj){
var returnValue='';
for(var key in obj){
if(typeof obj[key]==='object'){
returnValue+='<div>'+key+'/\n';
returnValue+=showJson(obj[key]);
returnValue+='</div>';
} else{
returnValue+=key+'equal'+obj[key];
}
}
return returnValue;
}
document.getElementById('data').innerHTML= showJson(obj);
Also:
jsonSource should be a string to be properly parsable as JSON data
typo in docoument.getElementById('data').innerHTML=returnValue;
Some of your problems:
jsonSource is already an object
you try to assign the returnValue in every call of showJson
Better to use a clean approach for looping and returning of the items:
var obj = { "error_code": 0, "ext_info": { "name": { "firstName": "John", "lastName": "Jonson", "nickName": "JJ" } } };
function showObj(obj) {
return Object.keys(obj).map(function (k) {
if (typeof obj[k] === 'object') {
return k + ':<br><div style="margin-left: 25px;">' + showObj(obj[k]) + '</div>';
}
return k + ': ' + obj[k];
}).join('<br>');
}
document.getElementById('data').innerHTML = showObj(obj);
<div id="data"></div>
// obj is the object to loop, ul is the ul to append lis to
function loop(obj, ul) {
$.each(obj, function(key, val) {
if(val && typeof val === "object") { // object, call recursively
var ul2 = $("<ul>").appendTo(
$("<li>").appendTo(ul)
);
loop(val, ul2);
} else {
$("<li>").text(val).appendTo(ul);
}
});
}
var ul = $("<ul>");
var jsonSource={"error_code":0, "ext_info":{"name":{"firstName":"John","lastName":"Jonson","nickName":"JJ"}}};
var data=JSON.parse(jsonSource)
loop(data, ul);
ul.addClass("my-new-list").appendTo('body');
I have an object which contains an unknown number of other objects. Each (sub-)object may contain boolean values as strings and I want to change them to real boolean values. Here's an example object:
var myObj = {
my1stLevelKey1: "true",
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: "true",
my4thLevelKey2: "false"
}
},
my2ndLevelKey2: {
my3rdLevelKey2: "FALSE"
}
}
}
What I want in the end is this:
var myObj = {
my1stLevelKey1: true,
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: true,
my4thLevelKey2: false
}
},
my2ndLevelKey2: {
my3rdLevelKey2: false
}
}
}
Important is that the number sub-objects/levels is unknown. How can I do this effectively by either using classic JavaScript or Mootools?
Recursion is your friend
(function (obj) { // IIFE so you don't pollute your namespace
// define things you can share to save memory
var map = Object.create(null);
map['true'] = true;
map['false'] = false;
// the recursive iterator
function walker(obj) {
var k,
has = Object.prototype.hasOwnProperty.bind(obj);
for (k in obj) if (has(k)) {
switch (typeof obj[k]) {
case 'object':
walker(obj[k]); break;
case 'string':
if (obj[k].toLowerCase() in map) obj[k] = map[obj[k].toLowerCase()]
}
}
}
// set it running
walker(obj);
}(myObj));
The obj[k].toLowerCase() is to make it case-insensitive
Walk each level of the object and replace boolean string values with the appropriate booleans. If you find an object, recurse in and replace again.
You can use Object.keys to grab all the members of each object, without worrying about getting inherited properties that you shouldn't.
var myObj = {
my1stLevelKey1: "true",
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: "true",
my4thLevelKey2: "false"
}
},
my2ndLevelKey2: {
my3rdLevelKey2: "FALSE"
}
}
};
function booleanizeObject(obj) {
var keys = Object.keys(obj);
keys.forEach(function(key) {
var value = obj[key];
if (typeof value === 'string') {
var lvalue = value.toLowerCase();
if (lvalue === 'true') {
obj[key] = true;
} else if (lvalue === 'false') {
obj[key] = false;
}
} else if (typeof value === 'object') {
booleanizeObject(obj[key]);
}
});
}
booleanizeObject(myObj);
document.getElementById('results').textContent = JSON.stringify(myObj);
<pre id="results"></pre>
JavaScript data structures elegantly can be sanitized by recursive functional reduce approaches.
var myObj = {
my1stLevelKey1: "true",
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: "true",
my4thLevelKey2: "false"
}
},
my2ndLevelKey2: {
my3rdLevelKey2: "FALSE"
}
}
};
myObj = Object.keys(myObj).reduce(function sanitizeBooleanStructureRecursively (collector, key) {
var
source = collector.source,
target = collector.target,
value = source[key],
str
;
if (value && (typeof value == "object")) {
value = Object.keys(value).reduce(sanitizeBooleanStructureRecursively, {
source: value,
target: {}
}).target;
} else if (typeof value == "string") {
str = value.toLowerCase();
value = ((str == "true") && true) || ((str == "false") ? false : value)
}
target[key] = value;
return collector;
}, {
source: myObj,
target: {}
}).target;
console.log(myObj);
Plain javascript recursion example:
function mapDeep( obj ) {
for ( var prop in obj ) {
if ( obj[prop] === Object(obj[prop]) ) mapDeep( obj[prop] );
else if ( obj[prop].toLowerCase() === 'false' ) obj[prop] = false;
else if ( obj[prop].toLowerCase() === 'true' ) obj[prop] = true;
}
};
And MooTools example, by extending the Object type with custom mapDeep() function:
Object.extend( 'mapDeep', function( obj, custom ) {
return Object.map( obj, function( value, key ) {
if ( value === Object( value ) )
return Object.mapDeep( value, custom );
else
return custom( value, key );
});
});
myObj = Object.mapDeep( myObj, function( value, key ) {
var bool = { 'true': true, 'false': false };
return value.toLowerCase() in bool ? bool[ value.toLowerCase() ] : value;
})
I've got an object containing user-data alongside some dates. I'd like to format these dates (as they are delivered like this 2015-02-13T18:25:37+01:00).
I'd like to have the values of the object changed in-place but how can I do this?
I traverse the object like this:
$.each(myObject, formatDates)
var isDate = function(value) {
return (value!==null && !isNaN(new Date(value)))
}
var formatDates = function(key, value){
if (isDate(value)) {
// Change value here
console.log("key:" + key + " value: " + value)
}
// Recursive into child objects
if (value !== null && typeof value === "object") {
$.each(value, formatDates)
}
}
You can use this
function iterate(obj) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterate(obj[property]);
} else {
// do your date thing
}
}
}
return obj;
}
iterate(object)
I have some JSON that looks like this:
{
"ST": "Security",
"C1": "Login failures",
"C2": "1",
"C3": {},
"P1": "2",
"P2": "administrator",
"P3": {},
"P4": {},
"DESCR": "failed login attempts",
"SID": "88",
"AV": "NO",
"SC": "0",
"CN": {}
}
I also have this jQuery loop to filter out values:
$.each(data, function(key, value) {
var innerArr = [];
$.each(value, function(innerKey, innerValue) {
innerArr.push(innerValue);
});
valueArr.push(innerArr);
});
The problem is that on items C3, P3, P4 & CN in my example, the each loop is pushing the value [object Object] into my value collection.
Is there a way to make these items empty strings rather than objects?
You could use:
...
if(typeof innerValue == "object") innerValue = JSON.stringify(innerValue);
valueArr.push(innerValue);
....
The stringify method of the JSON object turns an object into a string. The empty object {} will turn in "{}". If you want to add an empty string instead, use:
if(typeof innerValue == "object"){
innerValue = JSON.stringify(innerValue);
if(innerValue == "{}") innerValue = "";
}
valueArr.push(innerValue);
If you're 100% sure that your object is empty, you don't have to use JSON.stringify. typeof innerValue == "onject" would then be sufficient, to check whether you have to add "" instead of innerValue.
An alternative method to check whether an object is empty or not:
if(typeof innerValue == "object"){
var isEmpty = true;
for(var i in innerValue){
isEmpty = false;
break;
}
if(isEmpty) innerValue = "";
else {
//Object not empty, consider JSON.stringify
}
}
valueArr.push(innerValue);
$.each(data, function(key, value) {
var innerArr = [];
$.each(value, function(innerKey, innerValue) {
if (typeof innerValue == 'object') {
innerValue = '';
}
innerArr.push(innerValue);
});
valueArr.push(innerArr);
});
FYI, you can use .parseJSON function and get results easily
var obj = jQuery.parseJSON('{"ST":"Security"}');
alert( obj.ST === "Security" );
$.each(data, function(key, value) {
var innerArr = [];
$.each(value, function(innerKey, innerValue) {
innerValue = ($.isEmptyObject(innerValue)) ? '' : innerValue;
innerArr.push(innerValue);
});
valueArr.push(innerArr);
});
Edit:
If you didn't want to rely on jQuery's isEmptyObject() function, you could implement one yourself:
Object.size = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
// Get the size of an object
var size = Object.size(myArray);
$.each(data, function(key, value) {
var innerArr = [];
$.each(value, function(innerKey, innerValue) {
innerArr.push(innerValue);
});
//valueArr.push(innerArr);
valueArr.push(innerArr.join());
});
What is the inside loop for? It loops on each single letter of the String values.
$.each(data, function(key, value) {
var innerArr = [];
if (jQuery.isEmptyObject(value)) {
value = '';
}
...
});
Anyway you can use jQuery.isEmptyObject() to test easily if value is an empty object and modify it to an empty string.