Getting objects from json data tree, issue with reusing function - javascript

I wrote a code (with help from another question) in order to get objects from a json tree.
let objFound = null;
function getObjFromValue(obj, prop, val) {
Object.keys(obj).forEach((key) => {
if (objFound == null) {
if (key === prop && val === obj[key]) {
objFound = obj;
}
if (typeof obj[key] === 'object') {
getObjFromValue(obj[key], prop, val);
}
}
});
return objFound;
}
The problem with it is, that I can use it only once since the variable objFound is global (I need it to stop the forEach loop somehow) and never gets nulled again, so when I make another search I receive the previous and first object that the function found. Is there a way to improve/fix my function?

You can use a local variable to return the value, after setting the global variable to null.
let toReturn = objFound;
objFound = null;
return toReturn;
However, this global variable approach might not be the best one. The comment of #pilchard has a good alternative, making the variable local.

Related

Return value for each element called (accessed) (passed) (traversed) when using the dot notation in JavaScript

I want to do something like this:
let DOM = {
slide1: {
button: ".btn",
box: "#box"
}
}
and then when I write:
console.log(DOM.slide1.box);
I want the output to be "#slide1 #box".
After some research, I've tried to use the ES6 Proxy object, but the problem wasn't solved this way:
const dom_names = {
slide1: {
button: ".btn",
box: "#box"
}
}
const DOM_handler = {
get: (target, property, receiver)=>{
if(property == "slide1") return "#slide1";
return Reflect.get(...arguments);
}
}
const DOM = new Proxy(dom_names, DOM_handler);
After this implementation, I've recognized that I need to write the following to get the result I'm looking for:
console.log(`${DOM.slide1} ${DOM.create.box}) // #slide1 #box
As you can see, this is a very bad way, I was thinking of using my solution (using proxies), but the problem is: I need a way to know the "property of the property", I hope you can see and understand what I mean.
Some people will say: use your first implementation (the one without the proxies, meaning the first code in the question) but use for..of loop to get the key names. I will say, I want to use this exact code:
DOM.slide1.box
to get this output:
"#slide1 #box"
Why? why I am doing what I'm doing? Because I need a way to define the HTML classes and IDs in JavaScript, which makes life a lot easier when using document.querySelector. Also, the benefit of changing the classes and ID names in the HTML file, so when I change the classes and ID names in the HTML file, I will no longer need to re-factor (re) (rewrite) (change) the newer classes and IDs names inside the whole JavaScript file.
While a proxy can do this, there's a much simpler approach: just put the right string values in the properties of DOM in the first place. This still allows you to access them like you want:
const DOM = selectorHierarchy({
slide1: {
button: ".btn",
box: "#box"
}
});
function selectorHierarchy(obj, ancestors = []) {
for (var key in obj)
if (typeof obj[key] == "string")
obj[key] = [...ancestors, obj[key]].join(" ");
else if (typeof obj[key] == "object")
selectorHierarchy(obj[key], [...ancestors, '#'+key]);
return obj;
}
console.log(DOM.slide1.box)
This is possible. A naive approach is to apply proxies to every object recursively, then pass a private property along with each call, stringifying the traversal to a leaf.
Proof of concept:
const deepProxyAll = (obj, proxyObj) => {
for (const k in obj) {
if (typeof obj[k] === "object") {
obj[k] = deepProxyAll(obj[k], proxyObj);
}
}
return new Proxy(obj, proxyObj);
};
const proxyObj = {
get: (target, property, receiver) => {
if (!target.hasOwnProperty(property)) {
throw new Error(`property '${property}' does not exist on object`);
}
else if (typeof target[property] === "object" &&
typeof target[property] !== "null") {
target[property]._str = target._str ?
`${target._str} ${property}` : `#${property}`;
return target[property];
}
return target._str ? `${target._str} ${target[property]}`
: `#${target[property]}`;
}
};
const obj = {
slide1: {
button: ".btn",
box: "#box",
foobar: {bar: {quux: {garply: 42}}}
},
test: "hello",
};
const DOM = deepProxyAll(obj, proxyObj);
console.log(DOM.slide1.box);
console.log(DOM.slide1.foobar.bar.quux.garply);
console.log(DOM.test);
Proxies are powerful and it's temping to change the behavior of object property access to suit every whim, but think critically about whether the benefits are worth the downsides before adopting this sort of approach as a core design.

Add a default value for a json property when it doesn't exist in Javascript

So I'm trying to edit this rss feed with these 2 functions because of the media:content property which I have had no luck accessing directly. the functions I have below work for creating a new value called mediaContent which I can then easily access. The issue is in the rss feed not all objects will have media:content and I want to add a default value for the objects that don't have that property so I have consistency in my objects. Otherwise I end up with undefined on on some of mediaContent in my new object. I wanted to start just added a default value in when media:content is not present in the object but these ||'s are not working as I would have expected. How can I get my else if to punch in a default value if media:content does not exist? I'm probably missing something easy.
function getMediaContent(value) {
for (var i in value) {
if (i === "media:content") {
console.log("MC::", i)
return value[i].$;
} else if (i !== "title" || i !== "link" || i !== "pubDate" || i !== "isoDate" || i !== "guid" || i !== "contentSnippet" || i !== "content") {
debugger;
return "no media content"
}
}
}
function getNewsLinks() {
return newsItems.map(value => ({
value,
mediaContent: getMediaContent(value)
}))
}
SOLUTION (based on accepted answer)
function getMediaContent(value) {
return "media:content" in value ? value["media:content"].$ : "no media content";
}
works perfectly. Thanks!
Since you're just looking to see if a property exists on an object, you can use the in operator:
function getMediaContent(value) {
return "media:content" in value ? value["media:content"].$ : "no media content";
}
That checks if the property exists, and if so, gets the value of its $ property. Otherwise, returns the default value.
I needed something similar and optionally it would work for multi-layered JSON objects. Here is the function I use:
function getFromJSON(obj, ...args) {
for (const arg of args) {
if (!Array.isArray(arg)) {
if (arg in obj) {
obj = obj[arg]
} else {
return `${arg} not found in JSON`;
}
} else {
for (const argOpt of arg) {
if (argOpt in obj) {
obj = obj[argOpt]
break;
}
}
}
}
return obj
}
In addition, you can pass multiple keys in an array if you want to get the value of whichever exists.

Is there a simpler/cleaner way to write this?

I am using this code to sync with google storage. It works just want to know if there is a simpler and cleaner way to write this.
chrome.storage.sync.get(['titleCustom', 'textCustom', 'highCustom', 'quoteCustom', 'bgCustom'], (result) => {
if(result.titleCustom !== undefined){
document.documentElement.style.setProperty('--blue', result.titleCustom);
}
if(result.textCustom !== undefined){
document.documentElement.style.setProperty('--light-text', result.textCustom);
}
if(result.highCustom !== undefined){
document.documentElement.style.setProperty('--green', result.highCustom);
}
if(result.quoteCustom !== undefined){
document.documentElement.style.setProperty('--dark-text', result.quoteCustom);
}
if(result.bgCustom !== undefined){
document.documentElement.style.setProperty('--light-bg', result.bgCustom);
}
}
You could create a object mapping the key in result (e.g. titleCustom) to the respective color (e.g. --blue), then iterate through the object and so something like this:
// key:color are the variables of our outer for-each loop
if(result[key] !== undefined) {
document.documentElement.style.setProperty(color, result[key]);
}
This has the additional effect that you can use Object.keys(mapObject) as parameter in chrome.storage.sync.get instead of the hard-coded array.
Declare an object of key/values for the storage items where the key is the storage item and the value is the corresponding property you'd like to set for that key element.
let props = {
"titleCustom": "--blue",
"textCustom": "--light-text",
"highCustom" :"--green",
"quoteCustom": "--dark-text",
"bgCustom": "--light-bg"
}
];
let keys = Object.keys(props);
chrome.storage.sync.get(keys, (result) => {
keys.forEach((element) => {
if (element in result) {
document.documentElement.style.setProperty(props[element], result[element]);
}
});
});

In javascript return member of an object by reference

I have a json object which is variable (with possibly infinite sub-structures):
var myObject={
s1:{
s2:'2',
s3:'3'
},
s4:'4',
s5:{
s6:undefined
},
s7:'7'
};
I would like to find the first undefined member and to return it's reference,
to be able to modify it anytime (by event triggered functions)
function findFirstUndefined(obj){
for (var key in obj){
if (typeof(obj[key]) === 'object'){
var recursion=findFirstUndefined(obj[key])
if (recursion!==false){
return recursion;
}
}
else if (obj[key]===undefined){
return obj[key];
}
}
return false;
}
But it doesn't return the reference of the member, but it's value:
var subObj=findFirstUndefined(myObject);
subObj="my updated cool var!!";
doesn't modify the object.
I found that a library like JsonPath could do the trick,
but it seems to me kind of heavy for such a simple task.
Wouldn't it exists an elegant way to do so? Thank's!
JS doesn't do that. You could instead return a function that will assign to the missing entry?
return function(v) { obj[key] = v; };
In JS you cannot control manually if the value is passed, or the reference.
And only objects are passed by reference.
So unfortunately you just cannot do that.
As you requested it in comments I'll post a way of getting/setting these objects based on a string path. It's up to you whether you think it's too complicated of course, but here it is.
function getValue(object, path) {
value = object[path[0]];
if (path.length > 1) {
return getValue(value, path.slice(1, path.length))
}
return value;
}
function setValue(object, path, value) {
if (path.length > 1) {
setValue(object[path[0]], path.slice(1, path.length), value);
}
else {
object[path[0]] = value
}
}
You then use them like this: getValue(myObject, ["s1", "s2"]); / setValue(myObject, ["s1", "s2"], value);. I haven't had a chance to test the code, but the approach should hold.
If you return obj[key] you are returning a value obviously but try to return an array of two values [obj, key] and you have a way of referencing that node. Or from your recursive function you can build up a multidimensional array with these kinds of pairs for later use.
function findFirstUndefinedPath(obj){
for (var key in obj){
var path=[key];
if (typeof(obj[key]) === 'object'){
var subPath=findFirstUndefinedPath(obj[key]);
if (subPath!==false){
return path.concat(subPath);
}
}
else if (obj[key]===undefined){
return path;
}
}
return false;
}

Ember model to json

I am looking for an efficient way to translate my Ember object to a json string, to use it in a websocket message below
/*
* Model
*/
App.node = Ember.Object.extend({
name: 'theName',
type: 'theType',
value: 'theValue',
})
The websocket method:
App.io.emit('node', {node: hash});
hash should be the json representation of the node. {name: thename, type: theType, ..}
There must be a fast onliner to do this.. I dont want to do it manualy since i have many attributes and they are likely to change..
As stated you can take inspiration from the ember-runtime/lib/core.js#inspect function to get the keys of an object, see http://jsfiddle.net/pangratz666/UUusD/
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, ret = [];
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
} // ignore useless items
if (Ember.typeOf(v) === 'function') {
continue;
}
ret.push(key);
}
}
return this.getProperties.apply(this, ret);
}
});
Note, since commit 1124005 - which is available in ember-latest.js and in the next release - you can pass the ret array directly to getProperties, so the return statement of the getJson function looks like this:
return this.getProperties(ret);
You can get a plain JS object (or hash) from an Ember.Object instance by calling getProperties() with a list of keys.
If you want it as a string, you can use JSON.stringify().
For example:
var obj = Ember.Object.create({firstName: 'Erik', lastName: 'Bryn', login: 'ebryn'}),
hash = obj.getProperties('firstName', 'lastName'), // => {firstName: 'Erik', lastName: 'Bryn'}
stringHash = JSON.stringify(hash); // => '{"firstName": "Erik", "lastName": "Bryn"}'
I have also been struggling with this. As Mirko says, if you pass the ember object to JSON.stringify you will get circular reference error. However if you store the object inside one property and use stringify on that object, it works, even nested subproperties.
var node = Ember.Object.create({
data: {
name: 'theName',
type: 'theType',
value: 'theValue'
}
});
console.log(JSON.stringify(node.get('data')));
However, this only works in Chrome, Safari and Firefox. In IE8 I get a stack overflow so this isn't a viable solution.
I have resorted to creating JSON schemas over my object models and written a recursive function to iterate over the objects using the properties in the schemas and then construct pure Javascript objects which I can then stringify and send to my server. I also use the schemas for validation so this solution works pretty well for me but if you have very large and dynamic data models this isn't possible. I'm also interested in simpler ways to accomplish this.
I modifed #pangratz solution slightly to make it handle nested hierarchies of Jsonables:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
App.io.emit('node', {node: node.toJSON()});
Or if you have an ID property and want to include it:
App.io.emit('node', {node: node.toJSON({includeId: true})});
Will this work for you?
var json = JSON.stringify( Ember.getMeta( App.node, 'values') );
The false is optional, but would be more performant if you do not intend to modify any of the properties, which is the case according to your question. This works for me, but I am wary that Ember.meta is a private method and may work differently or not even be available in future releases. (Although, it isn't immediately clear to me if Ember.getMeta() is private). You can view it in its latest source form here:
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/utils.js
The values property contains only 'normal' properties. You can collect any cached, computed properties from Ember.meta( App.node, false ).cached. So, provided you use jQuery with your build, you can easily merge these two objects like so:
$.extend( {}, Ember.getMeta(App.node, 'values'), Ember.getMeta(App.node, 'cache') );
Sadly, I haven't found a way to get sub-structures like array properties in this manner.
I've written an extensive article on how you can convert ember models into native objects or JSON which may help you or others :)
http://pixelchild.com.au/post/44614363941/how-to-convert-ember-objects-to-json
http://byronsalau.com/blog/convert-ember-objects-to-json/
I modified #Kevin-pauli solution to make it works with arrays as well:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I also made some further modification to get the best of both worlds. With the following version I check if the Jsonable object has a specific property that informs me on which of its properties should be serialized:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, base, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
if (!Ember.isNone(this.get('jsonProperties'))) {
// the object has a selective list of properties to inspect
base = this.getProperties(this.get('jsonProperties'));
} else {
// no list given: let's use all the properties
base = this;
}
for (var key in base) {
if (base.hasOwnProperty(key)) {
v = base[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I am using this little tweak and I am happy with it. I hope it'll help others as well!
Thanks to #pangratz and #Kevin-Pauli for their solution!
Here I take #leo, #pangratz and #kevin-pauli solution a little step further. Now it iterates not only with arrays but also through has many relationships, it doesn't check if a value has the type Array but it calls the isArray function defined in Ember's API.
Coffeescript
App.Jsonable = Em.Mixin.create
getJson: ->
jsonValue = (attr) ->
return attr.map(jsonValue) if Em.isArray(attr)
return attr.getJson() if App.Jsonable.detect(attr)
attr
base =
if Em.isNone(#get('jsonProperties'))
# no list given: let's use all the properties
this
else
# the object has a selective list of properties to inspect
#getProperties(#get('jsonProperties'))
hash = {}
for own key, value of base
continue if value is 'toString' or Em.typeOf(value) is 'function'
json[key] = jsonValue(value)
json
Javascript
var hasProp = {}.hasOwnProperty;
App.Jsonable = Em.Mixin.create({
getJson: function() {
var base, hash, hashValue, key, value;
jsonValue = function(attr) {
if (Em.isArray(attr)) {
return attr.map(jsonValue);
}
if (App.Jsonable.detect(attr)) {
return attr.getJson();
}
return attr;
};
base = Em.isNone(this.get('jsonProperties')) ? this : this.getProperties(this.get('jsonProperties'));
json = {};
for (key in base) {
if (!hasProp.call(base, key)) continue;
value = base[key];
if (value === 'toString' || Em.typeOf(value) === 'function') {
continue;
}
json[key] = jsonValue(value);
}
return json;
}
});
Ember Data Model's object counts with a toJSON method which optionally receives an plain object with includeId property used to convert an Ember Data Model into a JSON with the properties of the model.
https://api.emberjs.com/ember-data/2.10/classes/DS.Model/methods/toJSON?anchor=toJSON
You can use it as follows:
const objects = models.map((model) => model.toJSON({ includeId: true }));
Hope it helps. Enjoy!
I have:
fixed and simplified code
added circular reference prevention
added use of get of value
removed all of the default properties of an empty component
//Modified by Shimon Doodkin
//Based on answers of: #leo, #pangratz, #kevin-pauli, #Klaus
//http://stackoverflow.com/questions/8669340
App.Jsonable = Em.Mixin.create({
getJson : function (keysToSkip, visited) {
//getJson() called with no arguments,
// they are to pass on values during recursion.
if (!keysToSkip)
keysToSkip = Object.keys(Ember.Component.create());
if (!visited)
visited = [];
visited.push(this);
var getIsFunction;
var jsonValue = function (attr, key, obj) {
if (Em.isArray(attr))
return attr.map(jsonValue);
if (App.Jsonable.detect(attr))
return attr.getJson(keysToSkip, visited);
return getIsFunction?obj.get(key):attr;
};
var base;
if (!Em.isNone(this.get('jsonProperties')))
base = this.getProperties(this.get('jsonProperties'));
else
base = this;
getIsFunction=Em.typeOf(base.get) === 'function';
var json = {};
var hasProp = Object.prototype.hasOwnProperty;
for (var key in base) {
if (!hasProp.call(base, key) || keysToSkip.indexOf(key) != -1)
continue;
var value = base[key];
// there are usual circular references
// on keys: ownerView, controller, context === base
if ( value === base ||
value === 'toString' ||
Em.typeOf(value) === 'function')
continue;
// optional, works also without this,
// the rule above if value === base covers the usual case
if (visited.indexOf(value) != -1)
continue;
json[key] = jsonValue(value, key, base);
}
visited.pop();
return json;
}
});
/*
example:
DeliveryInfoInput = Ember.Object.extend(App.Jsonable,{
jsonProperties: ["title","value","name"], //Optionally specify properties for json
title:"",
value:"",
input:false,
textarea:false,
size:22,
rows:"",
name:"",
hint:""
})
*/
Ember.js appears to have a JSON library available. I hopped into a console (Firebug) on one the Todos example and the following worked for me:
hash = { test:4 }
JSON.stringify(hash)
So you should be able to just change your line to
App.io.emit('node', { node:JSON.stringify(hash) })

Categories