I an working in javascript and stuck in understanding the objects.
Here is my scenario.
I have an object which in turn has multiple objects in it like.
data {
"aa" : object
"bb" : object
"cc" : object
}
//expanding aa bb and cc
aa {
name : "a"
type : "static"
value : "123"
}
bb {
name : "b"
type : "dyn"
value : "343"
}
cc {
name : "c"
type : "dyn"
value : "545"
}
Now what I want to achieve is that i have an object which should have those objects that have type = "dyn"
so i want to have a reqdata object like this
reqdata {
"bb" : object
"cc" : object
}
I have written a code to do this but it is not working as my reqdata has all the data.
var reqData = $.each (data, function(key, d){
if (type === "dyn")
return d;
});
Can any one guide me what the proper and efficient way of looping through the object.
Thanks any help and guidance will be appreciated
You need to create a new object, test the type property, and assign the current sub-object to the new one if the type is what you want.
// v--- Holds the results
var newObj = {};
// v--- The current sub-object
$.each(data, function(key, obj){
if (obj.type === "dyn") // <-- Test its `type` property
newObj[key] = obj; // <-- and assign to `newObj` if it matches
});
You should note that you're not making a copy of obj when you assign it to newObj. You're making a copy of a reference to obj.
This means that data and newObj share the same objects. Changes made via data are observable from newObj, and vice versa.
If you're used to functional programming, you can write your own filter function for objects:
function oFilter (obj, f) {
var result = {};
for (var x in obj) {
if (
obj.hasOwnProperty(x) &&
f(x,obj[x])
) {
result[x] = obj[x];
}
}
return result;
}
Then it'd be as you expected:
var reqData = oFilter(data, function(key,d){
if (d.type === "dyn") return true;
return false;
});
Similarly for map:
function oMap (obj, f) {
var result = {};
for (var x in obj) {
if (obj.hasOwnProperty(x)) {
result[x] = f(x,obj[x]);
}
}
return result;
}
Reduce doesn't make sense for objects though.
Shorter.
$(data).each( function(){
if(this.type === 'dyn'){ doStuff(this); }
} );
Now, IMO, constructor name is closer to type in JS. I'd build those objects with a function constructor names 'Dyn' and check <instance>.constructor.name for 'Dyn' but you would have to normalize for IE<=8 which involves parsing <instance>constructor.toString() so perhaps more trouble than it's worth.
But if you want to understand JS objects. Ditch the jQuery until you do. Learn how to use:
for(var x in object){
console.log('propertyLabel:' +x+', property:' + object[x]+'\n');
}
Then go back to understanding how jQuery itself works. Lots of juicy stuff under the hood there.
Related
I have a bunch of object attributes coming in as dot-delimited strings like "availability_meta.supplier.price", and I need to assign a corresponding value to record['availability_meta']['supplier']['price'] and so on.
Not everything is 3 levels deep: many are only 1 level deep and many are deeper than 3 levels.
Is there a good way to assign this programmatically in Javascript? For example, I need:
["foo.bar.baz", 1] // --> record.foo.bar.baz = 1
["qux.qaz", "abc"] // --> record.qux.qaz = "abc"
["foshizzle", 200] // --> record.foshizzle = 200
I imagine I could hack something together, but I don't have any good algorithm in mind so would appreciate suggestions. I'm using lodash if that's helpful, and open to other libraries that may make quick work of this.
EDIT this is on the backend and run infrequently, so not necessary to optimize for size, speed, etc. In fact code readability would be a plus here for future devs.
EDIT 2 This is NOT the same as the referenced duplicate. Namely, I need to be able to do this assignment multiple times for the same object, and the "duplicate" answer will simply overwrite sub-keys each time. Please reopen!
You mentioned lodash in your question, so I thought I should add their easy object set() and get() functions. Just do something like:
_.set(record, 'availability_meta.supplier.price', 99);
You can read more about it here: https://lodash.com/docs#set
These functions let you do more complex things too, like specify array indexes, etc :)
Something to get you started:
function assignProperty(obj, path, value) {
var props = path.split(".")
, i = 0
, prop;
for(; i < props.length - 1; i++) {
prop = props[i];
obj = obj[prop];
}
obj[props[i]] = value;
}
Assuming:
var arr = ["foo.bar.baz", 1];
You'd call it using:
assignProperty(record, arr[0], arr[1]);
Example: http://jsfiddle.net/x49g5w8L/
What about this?
function convertDotPathToNestedObject(path, value) {
const [last, ...paths] = path.split('.').reverse();
return paths.reduce((acc, el) => ({ [el]: acc }), { [last]: value });
}
convertDotPathToNestedObject('foo.bar.x', 'FooBar')
// { foo: { bar: { x: 'FooBar' } } }
Just do
record['foo.bar.baz'] = 99;
But how would this work? It's strictly for the adventurous with a V8 environment (Chrome or Node harmony), using Object.observe. We observe the the object and capture the addition of new properties. When the "property" foo.bar.baz is added (via an assignment), we detect that this is a dotted property, and transform it into an assignment to record['foo']['bar.baz'] (creating record['foo'] if it does not exist), which in turn is transformed into an assignment to record['foo']['bar']['baz']. It goes like this:
function enable_dot_assignments(changes) {
// Iterate over changes
changes.forEach(function(change) {
// Deconstruct change record.
var object = change.object;
var type = change.type;
var name = change.name;
// Handle only 'add' type changes
if (type !== 'add') return;
// Break the property into segments, and get first one.
var segments = name.split('.');
var first_segment = segments.shift();
// Skip non-dotted property.
if (!segments.length) return;
// If the property doesn't exist, create it as object.
if (!(first_segment in object)) object[first_segment] = {};
var subobject = object[first_segment];
// Ensure subobject also enables dot assignments.
Object.observe(subobject, enable_dot_assignments);
// Set value on subobject using remainder of dot path.
subobject[segments.join('.')] = object[name];
// Make subobject assignments synchronous.
Object.deliverChangeRecords(enable_dot_assignments);
// We don't need the 'a.b' property on the object.
delete object[name];
});
}
Now you can just do
Object.observe(record, enable_dot_assignments);
record['foo.bar.baz'] = 99;
Beware, however, that such assignments will be asynchronous, which may or may not work for you. To solve this, call Object.deliverChangeRecords immediately after the assignment. Or, although not as syntactically pleasing, you could write a helper function, also setting up the observer:
function dot_assignment(object, path, value) {
Object.observe(object, enable_dot_assignments);
object[path] = value;
Object.deliverChangeRecords(enable_dot_assignments);
}
dot_assignment(record, 'foo.bar.baz', 99);
Something like this example perhaps. It will extend a supplied object or create one if it no object is supplied. It is destructive in nature, if you supply keys that already exist in the object, but you can change that if that is not what you want. Uses ECMA5.
/*global console */
/*members split, pop, reduce, trim, forEach, log, stringify */
(function () {
'use strict';
function isObject(arg) {
return arg && typeof arg === 'object';
}
function convertExtend(arr, obj) {
if (!isObject(obj)) {
obj = {};
}
var str = arr[0],
last = obj,
props,
valProp;
if (typeof str === 'string') {
props = str.split('.');
valProp = props.pop();
props.reduce(function (nest, prop) {
prop = prop.trim();
last = nest[prop];
if (!isObject(last)) {
nest[prop] = last = {};
}
return last;
}, obj);
last[valProp] = arr[1];
}
return obj;
}
var x = ['fum'],
y = [
['foo.bar.baz', 1],
['foo.bar.fum', new Date()],
['qux.qaz', 'abc'],
['foshizzle', 200]
],
z = ['qux.qux', null],
record = convertExtend(x);
y.forEach(function (yi) {
convertExtend(yi, record);
});
convertExtend(z, record);
document.body.textContent = JSON.stringify(record, function (key, value, Undefined) {
/*jslint unparam:true */
/*jshint unused:false */
if (value === Undefined) {
value = String(value);
}
return value;
});
}());
it's an old question, but if anyone still looking for a solution can try this
function restructureObject(object){
let result = {};
for(let key in object){
const splittedKeys = key.split('.');
if(splittedKeys.length === 1){
result[key] = object[key];
}
else if(splittedKeys.length > 2){
result = {...result, ...{[splittedKeys.splice(0,1)]: {}} ,...restructureObject({[splittedKeys.join('.')]: object[key]})}
}else{
result[splittedKeys[0]] = {[splittedKeys[1]]: object[key]}
}
}
return result
}
I think I've seen how to create a JSON object without first preparing it. This is how i prepare it:
obj = {
0:{
type:{}
},
1:{},
2:{}
};
Now I think I can insert a value like: obj.0.type = "type0"; But I'd like to create it while using it: obj['0']['type'] = "Type0";.
Is it possible, or do I need to prepare it? I'd like to create it "on the fly"!
EDIT
I'd like to create JS object "On the fly".
var obj = {};
obj.test = "test"; //One "layer" works fine.
obj.test.test = "test" //Two "layers" do not work... why?
obj = {
0:{
type:{}
},
1:{},
2:{}
};
Now i think i can insert value like: obj.0.type = "type0";
I guess you mean "assign" a value, not "insert". Anyway, no, you can't, at least not this way, because obj.0 is invalid syntax.
But I'd like to create it while using it: obj['0']['type'] = "Type0";
That's fine. But you need to understand you are overwriting the existing value of obj[0][type], which is an empty object ({}), with the string Type0. To put it another way, there is no requirement to provide an initialized value for a property such as type in order to assign to it. So the following would have worked equally well:
obj = {
0:{},
1:{},
2:{}
};
Now let's consider your second case:
var obj = {};
obj.test = "test"; //One "layer" works fine.
obj.test.test = "test" //Two "layers" do not work... why?
Think closely about what is happening. You are creating an empty obj. You can assign to any property on that object, without initializing that property. That is why the assignment to obj.test works. Then in your second assignment, you are attempting to set the test property of obj.test, which you just set to the string "test". Actually, this will work--because strings are objects that you can set properties on. But that's probably not what you want to do. You probably mean to say the previous, string value of obj.test is to be replaced by an object with its own property "test". To do that, you could either say
obj.test = { test: "test" };
Or
obj.test = {};
obj.test.test = "test";
You are creating a plain object in JavaScript and you need to define any internal attribute before using it.
So if you want to set to "Type0" an attribute type, inside an attribute 0 of an object obj, you cannot simply:
obj['0']['type'] = "Type0";
You get a "reference error". You need to initialize the object before using it:
var obj = {
0: {
type: ""
}
};
obj['0']['type'] = "Type0";
console.log(obj['0']['type']);
You could create your own function that takes key as string and value and creates and returns nested object. I used . as separator for object keys.
function create(key, value) {
var obj = {};
var ar = key.split('.');
ar.reduce(function(a, b, i) {
return (i != (ar.length - 1)) ? a[b] = {} : a[b] = value
}, obj)
return obj;
}
console.log(create('0.type', 'type0'))
console.log(create('lorem.ipsum.123', 'someValue'))
Is it necessary to create nested objects before using it?
Yes it is, at least the parent object must exist.
Example:
var object = {};
// need to assign object[0]['prop'] = 42;
create the first property with default
object[0] = object[0] || {};
then assign value
object[0]['prop'] = 42;
var object = {};
object[0] = object[0] || {};
object[0]['prop'] = 42;
console.log(object);
Create object with property names as array
function setValue(object, keys, value) {
var last = keys.pop();
keys.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = value;
}
var object = {};
setValue(object, [0, 'prop'], 42);
console.log(object);
Recently I've found that I have had to create a object from attributes on a HTML tag. I am doing this in a AngularJS environment, so hyphenated attributes are converted to camelCase, but I could also do the same using data- attributes and dataset
So for example I have:
<element person-name="Grant" animation-jump="123" />
Which gives the object
{
"personName" : "Grant",
"animationJump" : "123"
{
My problem is that I then want to convert that camelCase object into a structured object:
{
"person" : {
"name" : "Grant" },
"animation" : {
"jump" : "123" }
}
I've created a JSFiddle of my QUint Unit Test https://jsfiddle.net/gdt3bonw/
It's actually working for the case I want which is only 1 level, but I would like to get it working for any number of levels because I foresee that it will be needed and so I can release the code publicly.
We will loop through the keys of the object using reduce, building up the result. We decompose each key into its components, such as personName into person and name. We loop over these components, creating subobjects if they do not already exist. Finally, we add the final component to the innermost subobject as a property with the value in question.
Object.keys(input).reduce((result, key) => {
var parts = key.match( /(^|[A-Z])[a-z]+/g) . map(part => part.toLowerCase());
var leaf = parts.pop();
var obj = result;
parts.forEach(part => obj = obj[part] = obj[part] || {});
obj[leaf] = input[key];
return result;
}, {});
You can't use that in this way, and I don't think that it would be a logic proposal. Below I explain why it wouldn't.
obj[["animation","jump"]] = "123"
replace it with
obj["animation"]["jump"] = "123"
and it's all fine.
Why I don't support your idea?
It's messy to use, there is no style in doing that.
There is no logic in using an array as an object key
There is another way of calling an object item by key: using a dot, and that won't support your idea. I think everyone can imagine why.
Why do you need to convert the attribute to camelCase in the first place..? Just do
function arrayToStructuredObject(obj,props){
if (props.length){
obj[props[0]] = props.length > 1 ? {} : value;
arrayToStructuredObject(obj[props.shift()],props);
}
return obj;
}
var props = "animation-jump-tremendous-pinky".split("-"),
value = "123",
obj = {},
sobj = {};
sobj = arrayToStructuredObject(obj, props);
Besides i would like to remind that using the bracket notation to create a property is only possible if the reference that the bracket notation is used upon is predefined as an object. Such as
var o1; // <- undefined
o1["myProp"] = 1; // Uncaught TypeError: Cannot set property 'myProp' of undefined
while
var o2 = {}; // Object {}
o2["myProp"] = 1; // <- 1
then again
o2["myProp"]["myOtherProp"] = 2; // <- 2 but won't type coerce o2.myProp to Object
So speaking of proposals, i am not sure if utilizing bracket notation directly over undefined variables yet as another object creation pattern makes sense or not.
Well in any case one complete solution would be
var inp = {"personName" : "Grant", "animationJump" : "123", "fancyGirlTakesARide" : "987"},
result = Object.keys(inp).reduce(function(p,c,i){
var props = c.replace(/[A-Z]/g, m => "-" + m.toLowerCase()).split("-");
return arrayToStructuredObject(p,props,inp[c])
},{});
function arrayToStructuredObject(obj,props,val){
if (props.length){
obj[props[0]] = props.length > 1 ? {} : val;
arrayToStructuredObject(obj[props.shift()],props,val);
}
return obj;
}
Though I loved the method of splitting the camelCase props by a look-ahead (/?=[A-Z]/) it takes an extra job of lower casing the whole array of prop strings regardless they are already lowercase or not. So i guess this might be slightly faster. (..or not due to the recursive nature of it)
This is not the best solution, but you can actually use arrays as key, in this particular situation, by converting them to a string:
obj[["animation","Jump"].join()] = "123";
This will work with your original object.
A solution, which uses Regex to split camel case string.
var obj = { "animationsJump": "123", "animationsRun": "456", "animationsHide": "789", "personName": "Grant", "personPetsDog": "Snowy", "personPetsCat": "Snowball" },
newObject = {};
Object.keys(obj).forEach(function (k) {
var path = k.split(/(?=[A-Z])/).map(function (s) {
return s.toLowerCase();
}),
last = path.pop();
path.reduce(function (r, a) {
r[a] = r[a] || {};
return r[a];
}, newObject)[last] = obj[k];
});
document.write('<pre>' + JSON.stringify(newObject, 0, 4) + '</pre>');
I need to find the most efficient way to remove values from a arbitrarily nested javascript object based on a list of 'keys-to-remove'. i.e.
var obj = {a:1, b:2, c:{d:1, e:1}};
var ignoreList = ["a","e"] (could also be ['a', 'c.e'])
removeIgnoredValues(obj, ignoreList) => {b:2, c:{d:1}}.
Now obviously this is easy enough to do if you don't care about efficiency, and my current implementation has been serving me well up till now. But now I'm having to deal with objects that have 6 levels and large arrays of data.
If anyone has a solution or link to one that would be awesome :)
Cheers
EDIT: Current implementation looks like this. It works (and deals with circular references). But is too slow.
/**
* Returns a sanitised string of an object, removing any functions and unwanted properties.
* #param {int} obj. The object to be stringified
* #param {Array[]} ignoreList. A array of object properties that should be removed.
*/
function sanitise(obj, ignoreList){
if(obj == undefined){
throw "Can't sanitise an undefined object"
}
var entry = JSON.parse(JSON.stringifyOnce(obj));
for(var i in entry){
if(entry.hasOwnProperty(i)){
if(contains(ignoreList, i)){
delete entry[i];
} else if(typeof(entry[i]) == "object" && entry[i] != null){
entry[i] = sanitise(entry[i], ignoreList);
}
}
}
return entry;
}
JSON.stringifyOnce = function(obj, replacer, indent){
var printedObjects = [];
var printedObjectKeys = [];
function printOnceReplacer(key, value){
var printedObjIndex = false;
printedObjects.forEach(function(obj, index){
if(obj===value){
printedObjIndex = index;
}
});
if ( key == ''){ //root element
printedObjects.push(obj);
printedObjectKeys.push("root");
return value;
}
else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
if ( printedObjectKeys[printedObjIndex] == "root"){
return "(pointer to root)";
}else{
return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase() : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
}
}else{
var qualifiedKey = key || "(empty key)";
printedObjects.push(value);
printedObjectKeys.push(qualifiedKey);
if(replacer){
return replacer(key, value);
}else{
return value;
}
}
}
return JSON.stringify(obj, printOnceReplacer, indent);
};
Okay, figured out a pretty nice way. You just make an ignore list with roughly the same object structure as the object to be ignored.
function ignore(obj, list){
for(var i in list){
var type = Object.prototype.toString.call(list[i]);
if(type == "[object String]"){
delete obj[i];
}
else if (type == "[object Object]"){
ignore(obj[i], list[i])
}
else if (type == "[object Array]"){
var objList = obj[i];
var subList = list[i][0];
for(var n in objList){
ignore(objList[n], subList)
}
}
}
}
x = {a:1, b:[{c:1, d:1}, {c:1, d:1}, {c:1, d:1}], e:1}
ignoreList = {'e':'e', 'b':[{'c':'c'}]}
ignore(x, ignoreList) => {a:1, b:[{d:1}, {d:1}, {d:1}]}
We can create a look-up table object for the original object, to delete any given key in O(1) time. The implementation will involve you adding custom functions to add/remove from the object.
(function() {
var lookUpTable = {};
myObj.prototype.insert = function(key, value) {
// add key to the myObj
// insert an Entry for parent of key in lookUpTable
// lookUpTable = { "a" : [myObj.b, myObj, myObj.c.d.e] }
}
myObj.prototype.ignore = function(ignoreList) {
for( key in ignoreList ) {
for( parent in lookUpTable[key] )
delete parent[key];
delete lookUpTable [key];
}
}
}());
Now you can call the insert function to insert the key-value:
myObj.insert('a.b.c.d', ['p', 'g']);
and call the ignore function to delete the object:
myObj.ignore(['d', 'e']);
Sorry for just giving the incomplete code. But, you should be able to implement the details quiet easily. Hope you get the idea.
For the example given by you:
obj = {a:[{b:1, c:1}, {b:1, c:1}, {b:1, c:1}]
and you want to ignore all the 'b's. Note that the lookup table entry values were arrays and not just a single value. This is where the power of ignoring multiple entries with the same name, comes in. In this case, the entry for 'b' would be something like this.
lookupTable = {
b : [ // The parent objects of 'b'
obj['a'][0],
obj['a'][1],
obj['a'][2]
]
}
Basically, the lookuptable holds an array of references to all the objects that contain the key 'b'. So, you iterate through each of these parent objects and delete their 'b' entry.
$.each(lookupTable['b'], function( parent ) {
delete parent['b']; // Deletes 'b' inside of every parent object
});
You populate this lookup table entry while inserting into obj or while obj is loaded for the first time. If obj is hard-coded, you could also generate the lookupTable once and hard-code it. Probably in along with your minify Javascript scripts. Although populating it at run-time is also quiet fine.
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"