Suppose I have an object like that.
var t = {
"obj1": {
"obj1.1": "test",
"obj1.2": {
"obj1.1.1": null, // <-- "obj1.1.1" has to have a value.
"obj1.1.2": "test"
}
}
};
And a path to the node where I'd like to add a value, i.e.:
var path = ['obj1', 'obj1.1', 'test'];
How do I add the value programmatically?
Try this:
function setDeepValue(obj, value, path) {
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
var obj = {};
var path = ['obj1', 'obj1.1'];
setDeepValue(obj, 'test', path);
console.log(obj); // {"obj1":{"obj1.1":"test"}}
You will however need to fix your object:
var t = {
"obj1": {
"obj1.1": "test",
"obj1.2": {
"obj1.1.1": null, // <-- "obj1.1.1" has to have a value.
"obj1.1.2": "test"
}
}
};
A object can't have a key without a value, so "obj1.1.1" will have to have a value, even if it's null.
Supposing this object
var obj = {
"obj1" : {
"obj1.1" : "",
"obj1.2" : {
"obj1.1.1" : "",
"obj1.1.2" : ""
}
}
}
var path = ['obj1', 'obj1.1', 'test'];
the algorithm is:
var el = obj;
for(var i = 0; i < path.length-2; i++) {
el = el[path[i]];
}
el[path[i]] = path[i+1];
or as function
function setpath(obj,path) {
var el = obj;
for(var i = 0; i < path.length-2; i++) {
console.log(i+"="+path[i]);
el = el[path[i]];
}
el[path[i]] = path[i+1];
console.log(el);
}
setpath(obj,['obj1', 'obj1.1', 'test']);
var tree = {
"obj1": {
"obj1.1": "test",
"obj1.2": {
"obj1.1.1": null,
"obj1.1.2": "test"
}
}
};
var path = ['obj1', 'obj1.1', 'test'];
A static way of setting 'test':
tree[path[0]][path[1]] = path[2];
A static way of replacing 'test' with newObj:
var newObj = { ... };
tree[path[0]][path[1]][path[2]] = newObj;
Related
There is a complex object and based on an array which is given as an input I need to modify its properties. Illustration is shown below. If the "field" is same , add them to "or" array .If its different "field" add them to "and" array along with its "value". I am using Set to get keys from both source and input and using them to group based on its keys. Also whenever there are duplicates .ie., suppose the "filterObj" already has the same (field, value) pair. Be it in "and" or inside "or",Then don't add it in the final object
Sandbox: https://codesandbox.io/s/optimistic-mirzakhani-pogpw-so-dpvis
There is a TestCases file in the sandbox which its needs to pass
let filterObj = {
feature: "test",
filter: {
and: [{ field: "field2" }]
}
};
let obj = [{ field: "field2", value: "3" }];
let all_filters = [];
if (filterObj.filter.and && filterObj.filter.and.hasOwnProperty("or")) {
all_filters = [...filterObj.filter.and.or];
} else if (filterObj.filter.and) {
all_filters = [...filterObj.filter.and];
}
const all_objs = [...obj, ...all_filters];
const uniqKeys = all_objs.reduce(
(acc, curr) => [...new Set([...acc, curr.field])],
[]
);
const updateItems = uniqKeys.map(obj => {
const filter_items = all_objs.filter(item => item.field === obj);
let resultObj = {};
if (filter_items && filter_items.length > 1) {
resultObj.or = [...filter_items];
} else if (filter_items && filter_items.length === 1) {
resultObj = { ...filter_items[0] };
}
return resultObj;
});
var result = { ...filterObj, filter: { and: [...updateItems] } };
console.log(result);
Try it.
I redid the implementation, it happened more universally.
Parses any filters according to your algorithm that it finds.
All test cases are worked.
Sandbox link: https://codesandbox.io/s/optimistic-mirzakhani-pogpw-so-i1u6h
let filterObj = {
feature: "test",
filter: {
and: [
{
field: "field1",
value: "2"
}
]
}
};
let obj = [
{
field: "field1",
value: "2"
},
{
field: "field1",
value: "1"
}
];
var FilterController = function(filter) {
var self = this;
self.filter = filter;
// encapsulated map of objects by fields
var storeMap = {};
// counter of objects
var counter = 0;
var tryPutObjectToMap = function(object) {
if (typeof object === "object") {
// get type for grouping
var objectType = self.getObjectGroupType(object);
if (objectType !== null) {
// cheack have group
if (!storeMap.hasOwnProperty(objectType)) {
storeMap[objectType] = [];
}
var duplicate = storeMap[objectType].find(function(sObject) {
return self.getObjectValue(sObject) === self.getObjectValue(object);
});
// check duplicate
if (duplicate === undefined) {
counter++;
storeMap[objectType].push(object);
} else {
// TODO: Handle duplicates
}
} else {
// TODO: handle incorrect object
}
}
};
// get filter structure from map
var getFilterStructureFromMap = function() {
var result = {};
// check exists root filter and filed if have objects
if (counter > 0) {
result["and"] = [];
}
for (var key in storeMap) {
if (storeMap.hasOwnProperty(key)) {
var array = storeMap[key];
if (array.length > 1) {
result["and"].push({
// clone array
or: array.slice()
});
} else {
result["and"].push(array[0]);
}
}
}
return result;
};
// rewrite and get current filter
// if you need^ create new object for result
self.rewriteAndGetFilter = function() {
self.filter.filter = getFilterStructureFromMap();
return self.filter;
};
// not prototype function for have access to storeMap
self.putObjects = function(objects) {
if (Array.isArray(objects)) {
// recursive push array elements
objects.forEach(element => self.putObjects(element));
// handle array
} else if (typeof objects === "object") {
// handle object
if (objects.hasOwnProperty("and") || objects.hasOwnProperty("or")) {
for (var key in objects) {
//no matter `or` or `and` the same grouping by field
// inner object field
if (objects.hasOwnProperty(key)) {
self.putObjects(objects[key]);
}
}
} else {
// filters props not found, try push to store map
tryPutObjectToMap(objects);
}
} else {
// TODO: Handle errors
}
};
if (self.filter.hasOwnProperty("filter")) {
// put and parse current objects from filter
self.putObjects(self.filter.filter);
}
};
// function for grouping objects.
// for you get filed name from object.
// change if need other ways to compare objects.
FilterController.prototype.getObjectGroupType = function(obj) {
if (typeof obj === "object" && obj.hasOwnProperty("field")) {
return obj.field;
}
return null;
};
// get object value
FilterController.prototype.getObjectValue = function(obj) {
if (typeof obj === "object" && obj.hasOwnProperty("value")) {
return obj.value;
}
return null;
};
var ctrl = new FilterController(filterObj);
ctrl.putObjects(obj);
var totalFilter = ctrl.rewriteAndGetFilter();
console.log(totalFilter);
console.log(JSON.stringify(totalFilter));
EDIT 1
I did not change the logic; I made a function based on it.
let filterObj = {
feature: "test",
filter: {
and: [
{
field: "field1",
value: "2"
}
]
}
};
let obj = [
{
field: "field1",
value: 2
},
{
field: "field1",
value: "1"
}
];
function appendToFilter(filter, inputObjects) {
var storeMap = {};
var counter = 0;
var handlingQueue = [];
// if filter isset the appen to handling queue
if (filter.hasOwnProperty("filter")) {
handlingQueue.push(filter.filter);
}
// append other object to queue
handlingQueue.push(inputObjects);
// get first and remove from queue
var currentObject = handlingQueue.shift();
while (currentObject !== undefined) {
if (Array.isArray(currentObject)) {
currentObject.forEach(element => handlingQueue.push(element));
} else if (typeof currentObject === "object") {
if (currentObject.hasOwnProperty("and") || currentObject.hasOwnProperty("or")) {
for (var key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
handlingQueue.push(currentObject[key]);
}
}
} else {
// TODO: append fild exists check
if (currentObject.field) {
if (!storeMap.hasOwnProperty(currentObject.field)) {
storeMap[currentObject.field] = [];
}
var localValue = currentObject.value;
// check duplicate
if (storeMap[currentObject.field].find(object => object.value === localValue) === undefined) {
counter++;
storeMap[currentObject.field].push(currentObject);
}
}
}
}
currentObject = handlingQueue.shift();
}
// create new filter settings
var newFilter = {};
// check exists root filter and filed if have objects
if (counter > 0) { newFilter["and"] = []; }
for (var storeKey in storeMap) {
if (storeMap.hasOwnProperty(storeKey)) {
var array = storeMap[storeKey];
if (array.length > 1) {
newFilter["and"].push({
// clone array
or: array.slice()
});
} else {
newFilter["and"].push(array[0]);
}
}
}
filter.filter = newFilter;
}
// update filterObj
appendToFilter(filterObj, obj);
console.log(filterObj);
EDIT 2,3 (UPDATED)
With others objects support.
export function appendToFilter(filter, inputObjects) {
var storeMap = {};
var others = [];
var counter = 0;
var handlingQueue = [];
// if filter isset the appen to handling queue
if (filter.hasOwnProperty("filter") && filter.filter.hasOwnProperty("and")) {
handlingQueue.push(filter.filter.and);
}
// append other object to queue
handlingQueue.push(inputObjects);
// get first and remove from queue
var currentObject = handlingQueue.shift();
while (currentObject !== undefined) {
if (Array.isArray(currentObject)) {
currentObject.forEach(element => handlingQueue.push(element));
} else if (typeof currentObject === "object") {
if (
currentObject.hasOwnProperty("and") ||
currentObject.hasOwnProperty("or")
) {
for (var key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
handlingQueue.push(currentObject[key]);
}
}
} else {
// TODO: append fild exists check
if (currentObject.field) {
if (!storeMap.hasOwnProperty(currentObject.field)) {
storeMap[currentObject.field] = [];
}
var localValue = currentObject.value;
// check duplicate
if (
storeMap[currentObject.field].find(
object => object.value === localValue
) === undefined
) {
counter++;
storeMap[currentObject.field].push(currentObject);
}
} else {
// handle others objects^ without field "field"
counter++;
others.push(currentObject);
}
}
}
currentObject = handlingQueue.shift();
}
// create new filter settings
var newFilter = {};
// check exists root filter and filed if have objects
if (counter > 0) {
newFilter["and"] = [];
}
for (var storeKey in storeMap) {
if (storeMap.hasOwnProperty(storeKey)) {
var array = storeMap[storeKey];
if (array.length > 1) {
newFilter["and"].push({
// clone array
or: array.slice()
});
} else {
newFilter["and"].push(array[0]);
}
}
}
// Append others to result filter
others.forEach(other => newFilter["and"].push(other));
filter.filter = newFilter;
}
I have the following functions with two console.log() calls.
get = function(path, obj){
for(var i = 0, path = path.split(/[\[\]\.]/), len = path.length; i < len; i++){
if(path[i]){
console.log(obj);
obj = obj[path[i]];
console.log(obj);
if(typeof obj === 'undefined'){
return '';
}
}
}
return obj;
};
set = function(path, obj, dta){
for(var i = 0, path = path.split(/[\[\]\.]/), len = path.length; i < len; i++){
if(path[i]){
obj = obj[path[i]];
if(typeof obj === 'undefined'){
return false;
}
}
}
obj = dta;
return true;
};
I then call them like this:
var data = {
user: {
secret: null,
accessToken: null
},
baseurl: 'http://api.example.com'
};
set('user', data, {/* new object data */});
get('user', data);
When the first console.log() is called, the object looks like this:
{
baseurl: "http://api.example.com",
user: {
accessToken: "12324",
secret: "123413421341342134"
}
}
Then when the second console.log() is called, the secret and accessToken look like this:
{
secret: null, accessToken: null
}
What is causing these to become null?
Your set() function does nothing. Assigning an object member to a variable, then setting that variable, doesn't affect the original object member.
You can keep track of the parent of the property-in-question, and use that to do your assignment:
set = function(path, obj, dta){
var parent = obj;
var selector = null;
for(var i = 0, path = path.split(/[\[\]\.]/), len = path.length; i < len; i++){
if(path[i]){
parent = obj;
selector = path[i];
obj = obj[path[i]];
if(typeof obj === 'undefined'){
return false;
}
}
}
if (parent && selector)
{
parent[selector] = dta;
return true;
}
return false;
};
var get = function(path, obj) {
for (var i = 0, path = path.split(/[\[\]\.]/), len = path.length; i < len; i++) {
if (path[i]) {
console.log(obj);
obj = obj[path[i]];
console.log(obj);
if (typeof obj === 'undefined') {
return '';
}
} // x
}
return obj;
};
var set = function(path, obj, dta) {
var parent = obj;
var selector = null;
for (var i = 0, path = path.split(/[\[\]\.]/), len = path.length; i < len; i++) {
if (path[i]) {
parent = obj;
selector = path[i];
obj = obj[path[i]];
if (typeof obj === 'undefined') {
return false;
}
}
}
if (parent && selector)
parent[selector] = dta;
return true;
};
var data = {
user: {
secret: null,
accessToken: null
},
baseurl: 'http://api.example.com'
};
set('user', data, {
secret: 1,
accessToken: 2
});
get('user', data);
i have a below json
{
"loanDetails": [
{
"vehicleDetail": {
"RCBookImageReferences": {
"imagePathReferences": [
{
}
]
}
},
"chargeDetails": [
{
}
],
"commissionDetails": [
{
}
],
"disbursementDetails": [
{
}
]
}
]
}
in the above json i need to traverse every key and if i find it emty then set the parent as empty array ie the output should be as below
{"loanDetails":[]}
i used the code below
function isEmpty(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return true;
}
But it did not give me the expected result.I'm stuck here any help will be much helpful.
The function clean takes an object and loops over its keys, calling clean recursively
on each object-valued property.
If the result of cleaning is an empty object, delete the key in question.
If the object itself turns out to be empty, return undefined, triggering deletion of the property holding that object at the higher level.
function clean(obj) {
var isEmpty = true;
for (var key in obj) {
var val = obj[key];
if (val === null || typeof val !== 'object' || (obj[key] = clean(val))) {
isEmpty = false;
} else {
delete obj[key];
}
}
return isEmpty ? undefined : obj;
}
>> a = { x: 1, b: { y: [] }, c: { d: { } } }
>> clean(a)
<< Object {x: 1}
This should make it recursive. With two solutions.
Solution 1: empty test function
var boolValue = true;
for(var prop in obj) {
if(obj.hasOwnProperty(prop) && typeof obj[prop] === 'object')
{
boolValue = recursiveIsEmpty(obj[prop]);
}
else
{
return false;
}
}
return boolValue ;
//test and set empty string
recursiveIsEmpty(jsonDataObj['loanDetails']) ? jsonDataObj['loanDetails'] = [] : null;
Solution 2 recursive empty function that empties parent obj
function recursiveIsEmpty(obj) {
var boolValue = true;
for(var prop in obj) {
if(obj.hasOwnProperty(prop) && typeof obj[prop] === 'object')
{
boolValue = recursiveIsEmpty(obj[prop]);
if (boolValue)
{
delete obj[prop]; //an object is empty. Delete from parent;
}
}
else
{
return false;
}
}
return boolValue; //returns an empty object
}
recursiveIsEmpty(jsonDataObj['loanDetails']) //returns jsonDataObj['loanDetails'] = [];
This checks if obj has a property that is an object. If so load that object and check it's properties. If not return false, because that will be string or number and that confirms the object is not empty.
Your JSON-string is not valid. When corrected, you can use a reviver function parameter (see MDN) to remove 'empty' arrays (aka properties with criteria you specify).
To be clear, the reviver function takes care of the traversing on all levels of the parsed object. If it returns undefined the property is removed from the object. The reviver used in the snippet thus removes all properties containing arrays with empty objects, or empty arrays.
The snippet demonstrates this.
// json string corrected
var foo = '{"loanDetails": [{"vehicleDetail": {"RCBookImageReferences": '+
'{"imagePathReferences": [{}]}}, "chargeDetails": [{}],'+
'"commissionDetails": [{}],"disbursementDetails": [{}]}]}';
// parse it, using reviver parameter
var fooparsed = JSON.parse( foo,
function (key, value) { //<= reviver here
return (value.length && value.length == 1 &&
value[0] instanceof Object &&
Object.keys(value[0]).length == 0) ||
value instanceof Array && !value.length
? undefined : value;
}
);
// print
Helpers.log2Screen( Object.print(fooparsed) );
<script src="http://kooiinc.github.io/JSHelpers/Helpers-min.js"></script>
if you are doing this using ajax then you should go with seriallizing the jason array using javascript.
at the time of passing data through json
data: "your data",
use this
data:$(form).serialize(),
it will pass all the key of that form which you are passing ,
if you want to see its result the try to print it on console
var inputObj = {
"loanDetails": [{
"vehicleDetail": {
"RCBookImageReferences": {
"imagePathReferences": [{}]
}
},
"chargeDetails": [{}],
"commissionDetails": [{}],
"disbursementDetails": [{}]
}, {
"vehicleDetail": {
"RCBookImageReferences": {
"imagePathReferences": [{
"Valid": "Working"
}]
}
},
"chargeDetails": [{}],
"commissionDetails": [{}],
"disbursementDetails": [{}]
}],
"Superman": {
"Name": ""
},
"SpiderMan": {
"Name": "Senthil"
}
}
function flatten(target, opts) {
var output = {},
opts = opts || {},
delimiter = opts.delimiter || '.'
function getkey(key, prev) {
return prev ? prev + delimiter + key : key
};
function step(object, prev) {
Object.keys(object).forEach(function(key) {
var isarray = opts.safe && Array.isArray(object[key]),
type = Object.prototype.toString.call(object[key]),
isobject = (type === "[object Object]" || type === "[object Array]")
if (!isarray && isobject) {
return step(object[key], getkey(key, prev))
}
output[getkey(key, prev)] = object[key]
});
if (Object.keys(object) == "") {
if (object instanceof Array) {
output[prev] = [];
} else {
output[prev] = {};
}
}
};
step(target)
return output
};
function unflatten(target, opts) {
var opts = opts || {},
delimiter = opts.delimiter || '.',
result = {}
if (Object.prototype.toString.call(target) !== '[object Object]') {
return target
}
function getkey(key) {
var parsedKey = parseInt(key)
return (isNaN(parsedKey) ? key : parsedKey)
};
Object.keys(target).forEach(function(key) {
var split = key.split(delimiter),
firstNibble, secondNibble, recipient = result
firstNibble = getkey(split.shift())
secondNibble = getkey(split[0])
while (secondNibble !== undefined) {
if (recipient[firstNibble] === undefined) {
recipient[firstNibble] = ((typeof secondNibble === 'number') ? [] : {})
}
recipient = recipient[firstNibble]
if (split.length > 0) {
firstNibble = getkey(split.shift())
secondNibble = getkey(split[0])
}
}
// unflatten again for 'messy objects'
recipient[firstNibble] = unflatten(target[key])
});
//Array Check
var keys = Object.keys(result);
if (keys.length > 0 && keys[0] === "0") {
var output = [];
keys.forEach(function(key) {
output.push(result[key])
});
return output;
}
return result
};
var flatted = flatten(inputObj);
var keys = Object.keys(flatted);
keys.forEach(function(key) {
if (JSON.stringify(flatted[key]) === "{}" || JSON.stringify(flatted[key]) == "") {
// console.log(key)
delete flatted[key];
var paths = key.split(".");
if (paths.length >= 2) {
var int = parseInt(paths[1])
if (isNaN(int)) {
key = paths[0];
flatted[key] = {};
} else {
key = paths[0] + "." + int;
flatted[key] = {};
}
var newKeys = Object.keys(flatted);
for (var j = 0; j < newKeys.length; j++) {
var omg = newKeys[j];
if (omg.indexOf(key) != -1 && omg.length > key.length) {
delete flatted[key];
}
}
}
}
})
console.log(flatted)
var output = unflatten(flatted);
alert(JSON.stringify(output))
I'm trying to write a function that can look up a namespace and value in a JavaScript object and return the key.
Image this data:
var o = {
map: {
lat : -33.86749,
lng : 151.20699,
zoom : 12
},
filters : {
animals : {
dogs: {
myDog : 'fido'
}
}
}
};
function get(namespace, key){
//TODO
}
get('filters.animals.dogs', 'myDog')
How would you build a function that does this dynamically - no matter the depth of the namespace?
This function is somewhat close, only it modifies the original object which we don't want ofcourse:
var get = function(obj, namespace, key, value) {
var parts = namespace.split('.');
for(var i = 0; i < parts.length; i++) {
if(typeof obj[parts[i]] == 'undefined'){
obj[parts[i]] = {};
}
obj = obj[parts[i]];
}
return obj[key] = value;
};
Reason for the madness is that I cannot expose the object. It must remain private and a public method must spit out a result.
Give this a try.
function get(namespace, key) {
var parts = namespace.split('.'),
i = 0,
l = parts.length,
obj = o;
while ( i < l ) {
obj = obj[parts[i]];
if ( ! obj ) break;
i++;
}
return obj && obj[key] ? obj[key] : null;
}
I have created a fiddle with working code. Hope that helps.
http://jsfiddle.net/RdhJF/2/
var o = {
map: {
lat : -33.86749,
lng : 151.20699,
zoom : 12
},
filters : {
animals : {
dogs: {
myDog : 'fido'
}
}
}
};
function get(obj, namespace)
{
var parts = namespace.split('.');
if(parts.length==0)
return -1;
var previousValue = obj;
for(var i = 0; i < parts.length; i++)
{
if(typeof previousValue[parts[i]] == 'undefined')
return -1;
else
previousValue = previousValue[parts[i]];
}
return previousValue;
}
var myValue= get(o,'filters.animals.dogs.myDog');
alert(myValue);
I want to remove and add an element from an array which is nested in a ko.observable object. I'm using the ko.mapping utility to map json data to my viewmodel. Inside the json data i have an array and it is this array that i want to remove and add an element from.
The add and remove functions are call from HTML bindings.
See my current code for doing this. It is not elegant at all, i know that, that is why i'm asking for help. How do i do see smarter?
function BaseViewModel() {
var self = this;
self.newItem = null;
self.selectedItem = ko.observable();
self.getNewItem = function () {
return self.newCleanItem(self.newItem);
}
self.read = function (search, callback) {
self.baseService.read(search, callback);
}
self.readCallback = function (data) {
if (self.newItem == null)
self.newItem = data;
self.selectedItem(data);
showInputContainer();
}
self.addLog = function () {
var item = new self.getNewItem();
var newItem = item.tLogs[0];
var currentSelectedItem = ko.mapping.toJS(self.selectedItem);
currentSelectedItem.tLogs.push(newItem);
self.selectedItem(currentSelectedItem);
showInputContainer(activeTab);
};
self.removeLog = function (item) {
var currentSelectedItem = ko.mapping.toJS(self.selectedItem);
currentSelectedItem.tLogs.pop(item);
vm.selectedItem(currentSelectedItem);
showInputContainer();
}
self.newCleanItem = function (data) {
for (var d in data) {
if (Object.prototype.toString.call(data[d]) === '[object Array]') {
var array = data[d];
for (var item in array) {
if (framework.baseFunctions().isNumeric(item)) {
for (var i in array[item]) {
array[i] = "";
}
}
}
data[d] = array;
}
else {
data[d] = "";
}
}
return data;
}
}
My jsondata could look that this:
jsondata = {
caseName: "test",
caseDescription: "This is a test",
tLogs: [
{
name: "log1",
date: "2013-03-19"
},
{
name: "log2",
date: "2013-02-02"
}
]
}