JavaScript object structure: how to access inner field with 'complex.key' - javascript

Given an object like this:
var obj = {
"name":"JonDoe",
"gender":"1",
"address":{
"phone":"1"
}
}
I know you can have such things :
console.log(obj['name']); // returns 'JonDoe'
My problem comes with the inner structure 'address', whose 'phone' inner field I would like to target with obj['address.phone'] but it returns instead undefined where all level 1 field return the matching value.
I am quite sure you could do it with some (de)serialisation function or any json lib, but I am am wondering if there's a smart way to list all inner structures like 'address' with no preliminary knowledge of which field I am going to target (like obj[field]).

Do:
var phone = obj.address.phone;
Bracket notation is typically used when using variables as the property name. If you know the properties, feel free to use dot notation (seen above)

You can try this:
var addresses = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
addresses.push(obj[key]['address']);
}
}
console.log(addresses);
For more complex object property querying use this library linq.js - LINQ for JavaScript
Update
If you want to list all address phones no matter how deep they are try recursive scanning:
var phones = [];
var scan = function(obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (key == 'address') {
phones.push(obj[key]['phone']);
}
else if (typeof obj[key] === 'object') {
scan(obj[key]);
}
}
}
};
scan(obj)
console.log(phones);

Related

Get an object just by a property value

Let´s assume I have an object property which is passed into a function. In this case 'name' is filled with 'myObject.name' (which has the value 'Tom') - so basically 'Tom' gets passed into the function as the 'name'
function(name) {
do something //non-essential for my question
}
Is it possible to get the object, where 'Tom' is the property of, just by having the information 'Tom'? Basically I´m looking to get myObject.
Thanks :)
No, that's not possible.
All that the function knows is that one of its parameters was pointed to the string "Tom", not what else points to that string somewhere else in memory.
You can store objects within an array, filter the array to match property name of object to parameter passed to function using for..of loop, Object.entries(), which returns an array of property, values of an object.
const data = Array();
const setObjectPropertyName = _name => {
data.push({[_name]:_name});
return data
}
const getObjectByPropertyName = prop => {
let res = `${prop} property not found in data`;
for (let obj of data) {
for (let [key] of Object.entries(obj)) {
if(key === prop) return obj;
}
}
return res;
}
let s = setObjectPropertyName("Tom");
let g = getObjectByPropertyName("Tom");
let not = getObjectByPropertyName("Tome");
console.log(s,"\n", g, "\n", not);
Disclaimer: you absolutely should not do this. I'm only posting this because it is in fact possible (with some caveats), just really not advisable.
Going on the assumption that this is running in the browser and it's all running in the global scope (like in a script tag), you could technically iterate over the window object, check any objects in window for a name property and determine if their name property matches the name passed to your function.
var myObject = {
name: 'Tom',
thisIs: 'so awful',
imSorry: true,
};
function doSomethingWithName(name) {
for (var obj in window) {
var tmp = window[obj];
if (Object(tmp) === tmp && tmp.name === name) {
return tmp;
}
}
}
console.log(doSomethingWithName(myObject.name));

Assigning nested values in (partially) undefined objects

Say I want to assign a value like this:
x.label1.label2.label3 = someValue;
// or equivalently:
x['label1']['label2']['label3'] = someValue;
This works as long as x.label1.label2 is defined but runs into reference errors otherwise. Which makes sense of course. But is there an easy way to assign this anyway where it simply creates the necessary nested objects?
So for example, if x equals { label1: {}, otherLabel: 'otherValue' } I want to update x to become { label1: { label2: { label3: someValue } }, otherLabel: otherValue }
I think I might be able to write a function myself, but is there a language feature or standard library function that does this?
is there a language feature or standard library function that does this
No. You have to write your own function or use a library that provides such functionality.
Related: How to set object property (of object property of..) given its string name in JavaScript?
This is partially possible using the Proxy class. You can wrap your object in a Proxy and override the get trap to create another copy of the same proxy when you access a nonexistent property. This lets you recursively create "deep" properties. An example:
let traps = {
get: function (target, name) {
if (!(name in target))
target[name] = new Proxy({}, traps);
return target[name];
}
};
let x = new Proxy({}, traps);
Then you would use x like any object, except it has this special behavior:
x.label1.label2.label3 = 'foo';
which creates a nested hierarchy of objects. However, note that this will create an object even if you access a nonexistent property. Thus, you will have to use the in keyword to check if it really contains a given property.
I think you should indeed use a custom function such as:
function assignByPath(obj, path, value) {
var field = path.split('>'),
last = field.pop();
field.reduce(
function(node, f) {
return node[f] = node[f] instanceof Object ? node[f] : {};
}, obj
)[last] = value;
}
var myObj = {};
assignByPath(myObj, 'label1>label2>label3', 'someValue');
console.log(myObj);
Theoretically, you could also override Object.prototype, which would allow you to do:
myObj.assignByPath('label1>label2>label3', 'someValue');
But I would not recommend that.
You can use Array.prototype.shift(), Object.assign(), recursion
var x = {
label1: {},
otherLabel: "otherValue"
};
var nestprops = (props, value, obj, o, curr = props.shift()) => props.length
? nestprops(props, value, (Object.assign(obj, {[curr]: {}}) && obj[curr]), o)
: ((!value || value) && (obj[curr] = value) && o);
console.log(nestprops(["label1", "label2", "label3"], "someValue", x, x));
Check length of keys inside label1 object if its equal to 0 then modify it to your desired object.
Here is a snippet, hope it helps.
var obj = { label1: {}, otherLabel: 'otherValue' };
if(Object.keys(obj.label1).length == 0 ) {
obj.label1 = { label2: { label3: "value3" } };
}
console.log(obj);

Most elegant way to save only primitive properties of a Javascript object

I have an object with lots of own and inherited methods. I need to make a simple snapshot of it with only primitive properties.
What's the most elegant way how to do it?
Javascript snippet
function copy(source) {
var target;
if (source) {
target = {};
for (var key in source) {
var prop = source[key],
type = typeof prop;
if (type === "string" || type === "number" || type === "boolean") {
target[key] = prop;
}
if (type == "object") {
target[key] = copy(prop);
}
}
}
return target;
}
With Underscore.js
function copy(source) {
var target;
if (source) {
target = {};
_.filter( _.keys(source), function( key ) {
var prop = source[key];
if (!_.isFunction(prop) && !_.isObject(prop)) {
target[key] = prop;
}
if (_.isObject(prop)) {
target[key] = copy(prop);
}
});
}
return target;
}
The problem here is that you are dealing with a mix of types that may or may not be what you want thanks to the loose typing that JavaScript offers. Specifically without walking any given array it is impossible to tell whether it only contains primitives and you might therefore want to keep it or whether it contains objects or functions that you want to filter out.
I realise library recommendations are the oldest hat, but I'm also going to strongly recommend that you use Underscore.js ( or a derivative such as Lodash ) which gives a whole lot of super-convenient functions that I use the whole time. In a very real sense these feel like the missing part of the core JavaScript language - in fact in some cases they are wrapping native functionality, but allowing you to avoid platform inconsistencies.
If you have Underscore included in your library then you can find the non-object property names of any given object like this:
var myObject = {
a: "hello",
b: "goodbye",
c: function() { console.log(this.b);}
d: { hello: "banana" }
};
_.filter( _.keys(myObject), function( key ) {
return ! _.isObject(myObject[key]);
}) // => ["a", "b"]
This could then be tidied into a function to clone the object, but of course it's unnecessary because Underscore offers us _.pick, which returns a copy of the object with only the whitelisted properties:
_.pick( myObject, _.filter( _.keys(myObject), function( key ) { return ! _.isObject(myObject[key]);}))
By flipping the !_.isObject you can retrieve all the properties which are objects, and perform a similar mapping on those if necessary. By wrapping both in a function you can call it recursively on the child objects you want to clone.
This answers your question, but be aware that this might not be the best way to perform undo/redo actions, which you mention in your comments is the use you plan to put this to. It may be easier to record actions and states onto a history stack recording only the actual changes any action performs, where they can be retrieved and reverted safely without needing to keep snapshots of your data.

Replacing underscore or lodash _.each with vanilla for loop

I'm wanting to replace the following code to no longer rely on the _.each() function of underscore.js or lodash.js:
function fset(data) {
_.each(dataDefault, function(item, key) {
var val = ( data[key] ? data[key] : dataDefault[key] );
$rootScope.meta[key] = val;
});
};
Ideally, I want to use a vanilla JavaScript for loop, but I don't understand how the _.each() function in underscore/lodash works to replace it...
Something like:
for(var i=0; i<data.length;i++) {
var val = ( data[key] ? data[key] : dataDefault[key] );
$rootScope.meta[key] = val;
}
But, I don't know how to get the key and item in this way...
dataDefault looks like:
var dataDefault = {
title: null,
description: null
};
An example of calling the function would be:
meta.fset({
title: 'Hello world',
description: 'DESC'
});
Try this:
Object.keys(dataDefault).forEach(function (key) {
var value = dataDefault[key]
// iteration code
})
With for..in you have to use hasOwnProperty to exclude inherit properties.
So, if I'm interpreting your logic correctly, what you're trying to do is loop through the keys in your defaults object, and if the object you're inspecting doesn't have that key, you want to add that key to the object and assign its value to the default value, is that correct? Any limitations on browser level?
The quickest way to do it if you know for sure what the default data object looks like would be to use a for..in loop:
var data = {}; // or wherever you get it from)
for (var key in defaultData){
data[key] = data[key] || defaultData[key];
}
That assumes that data.key is non-null and non-false. If false or null is a valid value (and your default is not null or false), then you'll want to make a bit more effort at ascertaining the existence of the key and type of the value. But based on your example, you're not worried about that.

How to find a property type from javascript object?

How can I find out which property of an object is an Array type?
Given the sample code below, I would expect to get the value OrderItemList.
function Order() {
this.Id = 0;
this.LocationID = 0;
this.OrderItemList = new Array();
}
var orderObject = new Order();
From what I understand, you have an Object in javascript and you want to know if it contains an Array or not. If that's what you want to achieve you can simply traverse through all the keys for that object and check if the value is an instanceOf array.
In Jquery, you could do something like this (updated Demo):
$.each( orderObject, function( key, value ) {
if(value instanceof Array){
console.log(key);
}
});
Javascript equivalent:
for (var key in orderObject) {
var val = orderObject[key];
if (val instanceof Array){
console.log(key);
}
}
I hope it gets you started in the right direction.
Edit - Like many have already pointed length attribute can not be used to uniquely distinguish an array from a string although you could do a typeof check to see if the value is a string.

Categories