I have nested object which can have any number of key at any depth.
I want to replace "." in all keys(if contain) with "#" How we can do this in efficient way.
Example Node js object
obj:{
"BotBuilder.Data.SessionState": {
"lastAccess": 1492886892545,
"version": 14,
"callstack": [
{
"id": "*:/",
"state": {
"BotBuilder.Data.WaterfallStep": 0,
"BotBuilder.Data.Intent": "welcomeDialog"
}
}
]
}
Currently i am using hard coded solution , but any keys can be possible in object at any level which contain "." I want generalize way to solve this problem
My code :
replaceDot:function(doc){
var finalobj={}
var finaldata={}
var finalcallstack=new Array();
console.log("doc==>",doc)
var callstack=doc["data"]["BotBuilder.Data.SessionState"]["callstack"]
for(var i = 0; i < callstack.length; i++) {
var tempcallstack={}
if("BotBuilder.Data.WaterfallStep" in callstack[i]["state"]){
tempcallstack["id"]=callstack[i]["id"]
var tempstate={}
tempstate["state"]=callstack[i]["state"]
tempstate["state"]["BotBuilder#Data#WaterfallStep"]=tempstate["state"]["BotBuilder.Data.WaterfallStep"]
tempstate["state"]["BotBuilder#Data#Intent"]=tempstate["state"]["BotBuilder.Data.Intent"]
delete tempstate["state"]["BotBuilder.Data.WaterfallStep"]
delete tempstate["state"]["BotBuilder.Data.Intent"]
tempcallstack["state"]=tempstate["state"];
finalcallstack.push(tempcallstack);
}
else{
finalcallstack.push(callstack[i]);
}
}
var obj={}
finalobj["lastAccess"]=doc["data"]["BotBuilder.Data.SessionState"]["lastAccess"]
finalobj["version"]=doc["data"]["BotBuilder.Data.SessionState"]["version"]
finalobj["callstack"]=finalcallstack;
obj["BotBuilder#Data#SessionState"]=finalobj
var secondrootobj={"BotBuilder#Data#SessionState":finalobj}
return secondrootobj;
}
Here's a function that takes an object or array, and target and replacement values for the keys of that object. It will then return a new object where instances of target are replaced with replacement (using String.prototype.replace) in the resulting object's keys.
var substituteKeyDeep = function(obj, target, replacement) {
// Get the type of the object. Array for arrays, Object for objects, null for anything else.
try {
var type = obj.constructor === Array ? Array
: (obj.constructor === Object ? Object : null);
} catch (err) {
// A try/catch is actually necessary here. This is because trying to access the `constructor` property
// of some values throws an error. For example `null.constructor` throws a TypeError.
var type = null;
}
if (type === Array) {
// Simply do a recursive call on all values in array
var ret = [];
for (var i = 0, len = obj.length; i < len; i++) {
ret[i] = substituteKeyDeep(obj[i], target, replacement);
}
} else if (type === Object) {
// Do a recursive call on all values in object, AND substitute key values using `String.prototype.replace`
var ret = {};
for (var k in obj) {
ret[k.replace(target, replacement)] = substituteKeyDeep(obj[k], target, replacement);
}
} else {
// For values that aren't objects or arrays, simply return the value
var ret = obj;
}
return ret;
};
var data = {
"BotBuilder.Data.SessionState": {
"lastAccess": 1492886892545,
"version": 14,
"callstack": [
{
"id": "*:/",
"state": {
"BotBuilder.Data.WaterfallStep": 0,
"BotBuilder.Data.Intent": "welcomeDialog"
}
}
]
}
};
var dataWithRenamedKeys = substituteKeyDeep(data, /\./g, '#');
console.log(dataWithRenamedKeys);
Note that in the example, the replacement value (/\./g) is a regex expression, not a string. This is because a regex expression with the global modifier (g) is required to replace ALL instances of the occurrence, not just the first, in the object's keys.
EDIT: Quick disclaimer! This solution will exceed the stack if substituteKeyDeep is called with an object that has circular references.
Related
I'm currently struggling with a JavaScript problem. I want to return a multi-level property, as well as every variable contained within, by passing in the original object, and an array of paths to the properties I want.
For example, if I have the following object:
obj = {
product: {
candidate: {
id: 10,
reference: "test",
count: 4,
steps: 10
}
}
}
I want to be able to call a method:
getVarPath(obj, ["product.candidate.ID", "product.candidate.reference"])
And then have it return one object with each variable passed in the array, in it's original structure. So this would return an object looking like so:
{
product: {
candidate: {
id: 10,
reference: "test"
}
}
}
I do have this working in my local solution at the moment (passing in one string rather than an array at the moment).
The solution at the moment is pretty horrid but I'm looking to improve it, so if anyone can think of a better method that would be great.
Again, this is pretty horrid right now but I'm looking to improve it. But it does the job:
var getVarPath = function(obj, keys){
var elements = keys.split("."),
evalStr = "",
objStr = "obj",
newObjStr = "newObj",
newObj = {};
if(elements.length > 1){
elements.forEach(function(key, index){
// first append a property accessor at the end of the eval string
evalStr = evalStr + "['" + key + "']";
// if we're at the last element, we've reached the value, so assign it
if(index === elements.length -1){
eval(newObjStr + evalStr + " = " + objStr + evalStr);
}
else {
// if we're not at the last, we're at an object level
// if the nested object doesn't exist yet, create it
if(!eval(newObjStr + evalStr)){
eval(newObjStr + evalStr + " = {};");
}
}
});
}
return newObj;
}
For each element in the input array:
First, you can split the initial string: var nestedElements="product.candidate.ID".split(.)"
This returns an array with each level: ["product","candidate","ID"]
Now you can access to your nested object using each element of the array: obj["product"]["candidate"]["ID"] either by using a loop over the array or recursion.
var currentobj=obj;
for (var i=0;i<nestedElements.length;i++){
currentobj=currentobj[nestedElements[i]]
}
// currentobj is your id
In the same process, you could dynamically add elements to a new obj using a similar process:
newobj={} //before loop
if (newobj["product"] === undefined) newobj["product"]={} //in loop
And that should be done for each element on the input array, in the end is iterating through arrays and accessing the object using strings
Your code as-is shouldn't actually work. You're treating keys as a string, but passing in an array. You can (and should) avoid using eval(), by keeping track of the "inner" objects you're currently looking at, and using object[property] notation instead of object.property.
function getVarPath(obj, keys) {
var result = {};
// ["product.candidate.id", "product.candidate.reference"]
keys.forEach(function(key) {
var src = obj, // inner source object
dest = result, // inner destination object
parts = key.split(/\./);
// e.g. ["product", "candidate", "id"]
parts.forEach(function(part) {
// if we're looking at an object, make sure it exists in the dest
if (typeof(src[part]) === "object")
dest[part] = dest[part] || {};
// if it's just a value, copy it
else
dest[part] = src[part];
dest = dest[part]; // move from obj to obj.product, then to obj.product.candidate, etc.
src = src[part];
});
});
return result;
}
var obj = {
product: {
candidate: {
id: 10,
reference: "test",
count: 4,
steps: 10
}
}
}
var output = getVarPath(obj, ["product.candidate.id", "product.candidate.reference"]);
console.log(JSON.stringify(output));
Using _.propertyOf(), Array#reduce(), and Object.assign(), as well as computed property names, you could create a less daunting implementation:
function getVarPath(object, paths) {
return paths.reduce(function (accumulator, path) {
const that = _.propertyOf(accumulator)
let walk = path.split('.')
let value = this(walk)
for (let key = walk.pop(); key !== undefined; key = walk.pop()) {
const base = that(walk)
value = { [key]: value }
if (base !== undefined) {
value = Object.assign(base, value)
}
}
return Object.assign(accumulator, value)
}.bind(_.propertyOf(object)), {})
}
let obj = {
product: {
candidate: {
id: 10,
reference: "test",
count: 4,
steps: 10
}
}
}
console.log(getVarPath(obj, ['product.candidate.id', 'product.candidate.reference']))
<script src="https://cdn.rawgit.com/lodash/lodash/4.17.4/dist/lodash.min.js"></script>
I have a json object which I would like to loop through; however each object has a nested object which I can't access through dot notation due to the values being unique.
.__proto__
will give me consistent results; however I'd like to pull out the values starting with the "-Jg". Is it possible to do this through a regular expression or another method?
Edit:
I'm looping through the 'javascript object' with angular
var lognew = [];
angular.forEach(log, function(value, key) {
if(value){
if(value.substr(0,3) !== "-Jg" ){
this.push(value);
}
}
}, lognew);
console.log(lognew);
This currently returns:
TypeError: undefined is not a function
Just enumerate using for in and look at the first 3 characters of the object's key
for(var key in jsonObj){
if( key.substr(0,3) !== "-Jg" ) continue;
var nestedObject = jsonObj[key];
}
angular edit
var log = { "1": { "-Jga": "b" }, "2": { "-Jgc": "d" } };
var lognew = [];
angular.forEach(log, function(value, key) {
if(key){
if(key.substr(0,3) !== "-Jg" ){
this.push(value);
}
}
}, lognew);
console.log(lognew);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
Say I have this object:
{
"prop1":"Hello",
"prop2":{
"prop1":{
"prop1":"Tablecloth",
"prop2":"Indians"
},
"prop2":"JuicyJuice"
},
"prop3":"Sponge",
"prop4":{"Bob":"Squarepants"}
}
I would like a recursive function that will return HelloTableclothIndiansJuicyJuiceSpongeSquarepants.
Whatever object I put it, I want it to cycle though until it gets all of the strings and adds them all up.
Thank you!
Here's a very simple implementation that should work for simple objects like this:
var walkProps = function(obj) {
var s = "";
for(var x in obj)
{
if(typeof obj[x] === "string")
s += obj[x];
else
s += walkProps(obj[x]);
}
return s;
}
Demonstration
Note, though, that that depends on the order in which for-in visits the properties on the object, which is not specified and can vary by engine and by how the object is constructed (for instance, the order in which the properties were added).
Update: With some slight modification, this can be used to return the values based on the alphabetical order of the keys. This method is not sensitive to implementation-dependent ordering of properties:
var walkProps = function(obj) {
var s = "", i = 0, keys = Object.keys(obj).sort(), i;
for(; i < keys.length; i++)
{
if(typeof obj[keys[i]] === "string")
s += obj[keys[i]];
else
s += walkProps(obj[keys[i]]);
}
return s;
}
So even if "prop3" comes before "prop2" it will still return the same output.
Demonstration
You would need to write a function that loops over an object's properties and see if they are a string, and then append the strings to an output. If the property is an object rather than a string, you would want to call the function on this object and append it's return value to your total output.
You can loop over an object's properties using for...in like:
var MyObject = {
'a': 'string1',
'b': 'string2'
};
for (var key in MyObject) {
var value = MyObject[key];
}
To check if a property is a string you would want to do:
typeof value === "string"
Which will return true/false accordingly.
As mentioned, for( var b in a ) may not preserve ordering:
// Return array of string values
function getStrings(a) {
if( typeof(a) == "string" ) return [a];
var list = [];
for( var b in a ) list = list.concat(getStrings(a[b]));
return list;
}
Applied to OP's data:
var a = {
"prop1":"Hello",
"prop2":{
"prop1":{
"prop1":"Tablecloth",
"prop2":"Indians"
},
"prop2":"JuicyJuice"
},
"prop3":"Sponge",
"prop4":{"Bob":"Squarepants"}
}
getStrings(a).join(); // "Hello,Tablecloth,Indians,JuicyJuice,Sponge,Squarepants"
// Or as asked for by OP (again, order is not guaranteed)
getStrings(a).join(''); // "HelloTableclothIndiansJuicyJuiceSpongeSquarepants"
Let's say I have this JSON data in a data variable
[{"id":"1","module_id":"1","title":"Test",
"start_date":"2012-11-12" "end_date":"2012-11-18"},
{"id":"8","module_id":"1","title":"This is OK",
"start_date":"2013-01-14","end_date":"2013-01-31"}]
How do I use underscore.js to get following result?
[{"id":"1","module_id":"1","title":"test",
"start_date":"2012-11-12","end_date":"2012-11-18"},
{"id":"8","module_id":"1","title":"this is ok",
"start_date":"2013-01-14","end_date":"2013-01-31"}]
Can I do this with invoke ?
You can do this easily with Lo Dash's _.mapValues function:
_.mapValues(objs, function(s){
return _.isString(s) ? s.toLowerCase() : s;
});
If you're already dealing with an object (or parsed JSON), you can loop and create a new one:
var objs = [{"id":"1","module_id":"1","title":"Test", "start_date":"2012-11-12", "end_date":"2012-11-18"},{"id":"8","module_id":"1","title":"This is OK", "start_date":"2013-01-14","end_date":"2013-01-31"}];
var out = [];
for(var i=0; i<objs.length; i++) {
var outObj = {}
for(var prop in objs[i]) {
var val = objs[i][prop];
if(prop === 'title') {
val = val.toLowerCase();
}
outObj[prop] = val;
}
out.push(outObj);
}
console.log(out);
http://jsfiddle.net/uY36J/
If you have array of objects:
for(var i = 0; i < array.length; i++) {
for (var prop in array[i])
// condition here
array[i][prop] = array[i][prop].toLowerCase();
}
console.log(array)
Same with underscore (I don't think it's much shorter - you still need two loops here. More readable - maybe, but not shorter)
_.each(array, function(obj) {
_.each(obj, function(value, key) {
// condition here
obj[key] = value.toLowerCase();
});
});
You can separate the object in two arrays, one with the keys and the other with the values, then use _.map to lowercase the strings.
var objs = [{"id":"1","module_id":"1","title":"Test", "start_date":"2012-11-12", "end_date":"2012-11-18"},{"id":"8","module_id":"1","title":"This is OK", "start_date":"2013-01-14","end_date":"2013-01-31"}];
_.map(objs,function(element) {
return _.object(_.keys(element), _.values(element).map(function(value) {
return _.isString(value)? value.toLowerCase():value;
}));
});
you see, I'm checking if it's a string we're dealing with, to avoid calling toLowerCase on other types.
Lowercasing both keys and values is trivial as well
_.map(objs,function(element) {
return _.object(
_.keys(element).map(function(key) {
return _.isString(key)? key.toLowerCase():key;
}),
_.values(element).map(function(value) {
return _.isString(value)? value.toLowerCase():value;
})
);
});
Caveat: this will not operate on nested objects. ¯\_(ツ)_/¯
I'm having trouble figuring out how to access a variable in a multilevel deep object using a function like
getLanguageVariable("form.passwordSwitch.disabled");
and the following object as sample
var language = {
"de": {
"form": {
"passwordSwitch": {
"enabled": "Der Klartext-Modus ist aus. Aktivieren?",
"disabled": "Der Klartext-Modus ist an. Deaktivieren?"
}
}
}
}
Tried to split the string at the dot character, then creating a string representation of
language["de"]["form"]["passwordSwitch"]["enabled"]
which is used to access objects and it's properties. I used this code:
var stack = variableIdentifier.split(".");
var reference = "";
for (i = 0; i < stack.length; i++) {
if (i == 0) reference += stack[i];
else reference += "[\"" + stack[i] + "\"]";
}
Any clues how to dynamically access the properites of an object, given you don't know how deep it is?
I implemented the same in pythons a couple of days ago. Basically, When you do not know how deep the object is, use a recursion pattern.
function getPath(obj, path)
{
path = path.split('.');
return _getpath(obj, path);
}
function _getPath(obj, path)
{
if(!path.length)
return obj;
p = path.shift();
if(obj[p])
return _getPath(obj[p], path);
return undefined;
}
You can do something like this;
function getLanguageVariable(path) {
// I don't know how you determine "de", but this should be
// easy to customise
var next = language.de;
// Make path = ["form","passwordSwitch","disabled"];
path = path.split(/\./);
// Loop over path, and for each pass, set next to the next key
// e.g. next = next["form"];
// next = next["passwordSwitch"]
// next = next["disabled"]
while (path.length && (next = next[path.shift()]) && typeof next === "object" && next !== null);
// Check we have used all the keys up (path.length) and return
// either undefined, or the value
return path.length ? undefined : next;
}
For future information, note what you have is an Object defined via Object Literal Syntax, and is not JSON at all; for more info see What is the difference between JSON and Object Literal Notation?