If I have a JSON, for example:
{
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
}
I want to access and modify some data but i don't know which one.
I'll have an array of keys like this:
["test2","test22"] and a value: "change to this".
I want to change the myjson.test2.test22 data to "change to this".
Is there some simple and elegant way to do this?
Note that I don't know what the array's content will be, so I cant use the myjson.test2.test22 access method.
Try this
function setDeep(obj, props, value) {
var cur = obj,
prop;
while ((prop = props.shift()) && props.length) {
cur = cur[prop]
}
cur[prop] = value
return obj;
}
var obj = {
"test1": {
"test11": "someting",
"test12": "something else"
},
"test2": {
"test21": "asdasd",
"test22": "qwasd"
}
}
setDeep(obj, ['test1', 'test12'], 'new value');
console.log(obj)
You could use Array#reduce and walk the path. Then assign the value to the object with the last key.
var object = { "test1": { "test11": "someting", "test12": "something else" }, "test2": { "test21": "asdasd", "test22": "qwasd" } },
path = ["test2", "test22"],
lastKey = path.pop();
path.reduce(function (o, k) {
return o[k];
}, object)[lastKey] = 'change to this';
console.log(object);
For unknow properties, i suggets to make a check befor and use a default object then.
var object = { "test1": { "test11": "someting", "test12": "something else" }, "test2": { "test21": "asdasd", "test22": "qwasd" } },
path = ["test2", "test22", "42"],
lastKey = path.pop();
path.reduce(function (o, k) {
(o[k] !== null && typeof o[k] === 'object') || (o[k] = {});
return o[k];
}, object)[lastKey] = 'change to this';
console.log(object);
Following is a recursive function the traverses the keys of an object until the matching key (last one in the array) is found then it's value is changed and the changed object is returned. base case is when the array of keys is empty. Returns false if the key is not found
given an object named obj
var obj = {
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
function changeAkey(object, arrayOfKeys, value){
if(arrayOfKeys.length == 0)
return value;
else{
if(!object[arrayOfKeys[0]])
return false;
else
object[arrayOfKeys[0]] = changeAkey(object[arrayOfKeys[0]], arrayOfKeys.slice(1), value);
return object;
}
}
so changeAkey(obj, ["test2", "test22"], value) would do the job
I would start by checking if the property exists and if it does change the data. This way it won't throw an unspecific error.
var keys = ["key1", "key2"];
var myObj = {
"random1": {
"random2": "someting",
"random3": "something else"
},
"key1": {
"random4": "asdasd",
"key2": "qwasd"
};
}
var hasKeys = myObj[keys[0]][keys[1]] || null;
if(hasKeys) {
myObj[keys[0]][keys[1]] = "some data";
} else {
console.log('property not found');
}
I would propose to serialize your object and use String replace(). It works for any depth and simple code.
var objStr = JSON.stringify(obj);
changes.forEach(function(item){
objStr = objStr.replace(item, to);
})
obj = JSON.parse(objStr);
var obj = {
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
}
var changes = ["test11", "test21"];
var to = 'change to this';
var objStr = JSON.stringify(obj);
changes.forEach(function(item){
objStr = objStr.replace(item, to);
})
obj = JSON.parse(objStr);
console.log(obj)
var obj = {
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
};
var keys = ["test2","test22"];
function update_obj(obj, keys, value) {
for (var i = 0; i < keys.length - 1; i++) {
obj = obj[keys[i]];
}
obj[keys[keys.length - 1]] = value;
}
update_obj(obj, keys, "hi");
console.log(obj);
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 need to convert "flat object" like this (input data):
{
'prop1': 'value.1',
'prop2-subprop1': 'value.2.1',
'prop2-subprop2': 'value.2.2',
}
to immersion object like this (output data):
{
'prop1': 'value.1',
'prop2': {
'subprop1': 'value.2.1',
'subprop2': 'value.2.2'
}
}
Of course solution have to be prepare for no-limit deep level.
My solution does not work:
var inputData = {
'prop1': 'value.1',
'prop2-subprop1': 'value.2.1',
'prop2-subprop2': 'value.2.2',
};
function getImmersionObj(input, value) {
var output = {};
if ($.type(input) === 'object') { // first start
$.each(input, function (prop, val) {
output = getImmersionObj(prop.split('-'), val);
});
} else if ($.type(input) === 'array') { // recursion start
$.each(input, function (idx, prop) {
output[prop] = output[prop] || {};
output = output[prop];
});
}
return output;
}
console.log(getImmersionObj(inputData)); // return empty object
Can you help me find the problem in my code or maybe you know another one, better algorithm for conversion like my?
You could use a function for spliting the path to the value and generate new objects for it.
function setValue(object, path, value) {
var way = path.split('-'),
last = way.pop();
way.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = value;
}
var object = { 'prop1': 'value.1', 'prop2-subprop1': 'value.2.1', 'prop2-subprop2': 'value.2.2' };
Object.keys(object).forEach(function (key) {
if (key.indexOf('-') !== -1) {
setValue(object, key, object[key]);
delete object[key];
}
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var input = {
"property1": "value1",
"property2.property3": "value2",
"property2.property7": "value4",
"property4.property5.property6.property8": "value3"
}
function addProp(obj, path, pathValue) {
var pathArray = path.split('.');
pathArray.reduce(function (acc, value, index) {
if (index === pathArray.length - 1) {
acc[value] = pathValue;
return acc;
} else if (acc[value]) {
if (typeof acc[value] === "object" && index !== pathArray.length - 1) {
return acc[value];
} else {
var child = {};
acc[value] = child;
return child;
}
} else {
var child = {};
acc[value] = child;
return child;
}
}, obj);
}
var keys = Object.keys(input);
var output = {};
keys.forEach(function (k) {
addProp(output, k, input[k]);
});
console.log(output);
I'm trying to create a JavaScript function that creates an object using strings for structure and fills it from DOM data.
For example, the following strings could look like this:
some.example.here = "hello"
some.example.there = "hi"
other.example = "heyo"
Which should create this object:
{
some: {
example: {
here: "hello",
there: "hi"
},
other: {
example: "heyo
}
}
The data as said comes from DOM and is being load at the code segment labeled "read data into object". The data loads fine and the object structure is being setup fine as well, but the data is not being put into the data field.
Here's the code for the function:
function getDataFromElement(element) {
obj = {};
$(element)
.find("[data-value]")
.each(function() {
// create object node
valueObj = {};
currentValueObj = valueObj;
$.each($(this).attr("data-value").split("."), function(i, objpath) {
currentValueObj[objpath] = {};
currentValueObj = currentValueObj[objpath];
});
// read data into object
if($(this).is("[data-putvalue]") && $(this).attr("data-putvalue") != "html") {
currentValueObj = $(this).attr($(this).attr("data-putvalue"));
} else {
currentValueObj = $(this).html();
}
console.log(currentValueObj);
// combine with previous gathered data
obj = $.extend(true, {}, obj, valueObj);
});
return obj;
}
Does anyone know what to do?
I would do it like this:
var createObject = function(model, name, value) {
var nameParts = name.split("."),
currentObject = model;
for (var i in nameParts) {
var part = nameParts[i];
if (i == nameParts.length-1) {
currentObject[part] = value;
break;
}
if (typeof currentObject[part] == "undefined") {
currentObject[part] = {};
}
currentObject = currentObject[part];
}
};
And then use it like that:
var model = {};
createObject(model, "some.example.here", "hello");
createObject(model, "some.example.there", "hi");
createObject(model, "other.example", "heyo");
Probably this can suit you (adapted from another project of mine, adapt and use as needed):
NOTE the element's name is taken as key and value as the value
function fields2model( $elements, dataModel )
{
$elements.each(function( ){
var $el = $(this),
name = $el.attr('name'),
key, k, i, o, val
;
key = name;
val = $el.val() || '';
k = key.split('.'); o = dataModel;
while ( k.length )
{
i = k.shift( );
if ( k.length )
{
if ( !o.hasOwnProperty( i ) ) o[ i ] = /^\d+$/.test( k[0] ) ? [ ] : { };
o = o[ i ];
}
else
{
o[ i ] = val;
}
}
});
}
Example use:
<input name="some.example.here" value="hello" />
<input name="some.example.there" value="hi" />
var model = {};
fields2model($('input,textarea,select'), model);
The example elements above will give the below model:
model = {
some: {
example: {
here: "hello",
there: "hi"
}
};
Some functional implementation:
const value = 'hello';
'some.example.here'.split('.').reverse().reduce((reduction, segment, index) => {
const result = {};
if (index === 0) {
result[segment] = value;
} else {
result[segment] = reduction;
}
return result;
}, {})
#theFreedomBanana +1
Works for me
const magicFunction = (string, value) =>
string
.split('.')
.reverse()
.reduce((acc, cur, index) => ({ [cur]: index === 0 ? value : acc }), {});
I have a JSON OBJECT similar to following
{
"kay1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33":"value33"
}
}
I want to replace that with JSON ARRAY as follows
[
"value1",
"value2",
[
"value31",
"value32",
"value33"
]
]
My motivation to change the the JSON OBJECT to JSON ARRAY is it takes less amount of network traffic, getting the value from ARRAY is efficient than OBJECT, etc.
One problem I face is the readability of the ARRAY is very less than OBJECT.
Is there any way to improve the readability?
Here you go, I have written a function which will convert all instances of object to array and give you the result you are expecting.
var objActual = {
"key1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33": {
"key331" : "value331",
"key332" : "value332"
}
}
};
ObjectUtil = {
isObject : function(variable) {
if(Object.prototype.toString.call(variable) === '[object Object]') {
return true;
}
return false;
},
convertToArray : function(obj) {
var objkeys = Object.keys(obj);
var arr = [];
objkeys.forEach(function(key) {
var objectToPush;
if(ObjectUtil.isObject(obj[key])) {
objectToPush = ObjectUtil.convertToArray(obj[key]);
} else {
objectToPush = obj[key];
}
arr.push(objectToPush);
});
return arr;
}
};
var result = ObjectUtil.convertToArray(objActual);
console.log(result);
In my opinion, it should be:
{
"kay1":"value1",
"key2":"value2",
"key3":["value31", "value32", "value33"]
}
Using the init method is time critical. So use a scheme or assign static JSON to Storage.keys and assign your bulk data array to store.data. You can use store.get("key3.key31") after that. http://jsfiddle.net/2o411k00/
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
var data = {
"kay1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33":"value33"
}
}
var Storage = function(data){
this.rawData = data;
return this;
}
Storage.prototype.init = function(){
var self = this;
var index = 0;
var mp = function(dat, rootKey){
var res = Object.keys(dat).map(function(key, i) {
var v = dat[key];
if (typeof(v) === 'object'){
mp(v, key);
} else {
self.data.push(v);
var nspace = rootKey.split(".").concat([key]).join(".");
self.keys[nspace] = index++;
}
});
}
mp(this.rawData, "");
}
Storage.prototype.get = function(key){
return this.data[this.keys[key]];
};
Storage.prototype.data = [];
Storage.prototype.keys = {};
var store = new Storage(data);
console.log(data);
store.init();
console.log("keys", store.keys);
console.log("data", store.data);
console.log("kay1=", store.get(".kay1"));
console.log("key2=", store.get(".key2"));
console.log("key3.key31=", store.get("key3.key31"));
console.log("key3.key32=",store.get("key3.key32"));
console.log("key3.key33=", store.get("key3.key33"));
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.