Making an object from JSON and giving it some methods in Javascript - javascript

I am returning JSON from an API via a $.ajax request and end up with a lump of JSON:
var result = {
"status": 200,
"offset": 5,
"limit": 25,
"total": 7,
"url": "/v2/api/dataset/topten?",
"results": [
{
"datasets": [
"dataset",
"tt_all"
],
"id": "Hxb6VtpFRQ9gEr",
"title": "Venues",
"type": "topten",
"url": "/v2/dataset/topten/Hxb6VtpFRQ9gEr"
},
}
Or something similar. There are nested arrays containing more results in larger requests.
I would like to parse this information, put it into an object and have methods for available for that object to extract specific bits of information, from all levels - something like:
result.title => "Venues" or result.id => "Hxb6v...."
However, the output from the AJAX request can be assigned to a var by a user defined so I would like to make a function to stick this in an object with methods available before it exits the ajax success function and get assigned to result or whatever.
I don't particularly want to go down the:
Object.prototype.method = function(){ // extend Object here }
method as it makes people angry.
If I make another object to extend the prototype:
function Datalump(){};
Datalump.prototype.title = function(){
// get title or something here
};
I am struggling with what to pass to what and assigning things to the wrong thing.
How do I go about this method / object creation?
Any suggestions or pointers would be greatly appreciated.
UPDATE: Thank you all for the help - it's been very enlightening. I've marked Mike Brant's answer as correct as it seems the most appropriate to the question I asked. George Jempty's answer was also a very useful learning experience.
I'm actually going in a slightly different direction in the project (new requirements!), but parts of all the answers will probably make it into the 'alpha'.
Many thanks all.

If you have a javascript object (like you get after your JSON is parsed into object), you can just add whatever methods you want to it like this:
result.getTitle = function() {
// return title value of interest
return this.results.title;
}
result.getId = function() {
// return id value of interest
return this.results.id;
}
Here result is the the object that you have after JSON is parsed.

Create a module that wraps the JSON result. The module will return the original result, plus any convenience methods you might need in addition. Then use underscore.js and in particular _.result to interact with the wrapped result. This way you won't need to care whether you are accessing one of the original properties of the wrapped result or one of the convenience methods.
var wrappedResult = WrappedResult(result);
var status = _.result(wrappedResult, 'status');
var foobar = _.result(wrappedResult, 'foobar');
If the convenience of _.result is outweighed by the verbosity, you can just call wrappedResult.status or wrappedResult.foobar() directly
Implementation of WrappedResult:
var WrappedResult = function(result) {
return _.extend({}, result, {
foobar: function() {
console.log('foobar');
}
}
}
Something like the above anyway; you might want to extend _.clone(result) instead.
BTW underscore.js is by no means necessary, though in this case it does a nice job of describing exactly what you are doing (you "_.extend" the result with some methods in addition to the initial properties). Instead, wrap your result with an object, and then add methods directly, similar to the other answer:
var WrappedResult = function(result) {
result.foobar = function() {
console.log('foobar');
};
return result;
}

Related

Access value of object array in javascript

I have managed to create an object connecting to an API. So I have a function loadColors()
loadColors = function() {
var Colors = [];
for (var i =0; i < array.length; i++) {
Card.get({cardName: array[i].object_name}, function(data) {
Colors.push(data.data[0].color);
});
}
return {Colors};
};
var Colors = loadColor();
and inside there I was able to see the result with console.log(Colors) which is:
Object {Colors: Array[0]}
Colors: Array[4]
0: "green"
1: "red"
2: "yellow"
3: "blue"
length: 4
__proto__: Array[0]
__proto__: Object
When I try to access a value like console.log[Colors[0]]; I get undefined.
What am I doing wring?
Why are you wrapping Colors in {} in your return statements?
Just do return Colors; and you would be able to access the indexed array items directly from the return result.
Since you're currently returning an object containing the array, you would have to access it by Colors.Colors[0].
Update
I realize that besides wrapping your result in an object before returning it, you are also returning an array that is not populated with items at the return point, due to the asynchronous nature of Card.get.
This problem is actually rather well suited for a concept called Promises, but rather than introducing another new concept at this point, I will illustrate how you can solve this manually. (IE doensn't have native Promise support, there are frameworks that will solve this but you may not want an entire new framework just for this. jQuery has something called Deferreds, but they're subtly different from the Promise specs).
Instead of returning a result from the function, the callback from Card.get should call the function that moves your code forward. It should only do this once the array is filled, however, and not once for every callback.
Card.get({ your options }, function(data) {
Colors.push(data.data[0].color);
if(Colors.length == array.length) {
allColorsLoaded(Colors);
}
});
So if your logic is currently:
var Colors = loadColors();
alert(Colors.length);
It would need to be updated so that everything that relies on Colors to be populated is initiated by the callback:
var allColorsLoaded = function(Colors) {
alert(Colors.length);
};
loadColors();
It is isn't so clear from the question what is going on but from what I understood, when you try
console.log(Colors[0])
outside the function it returns undefined while inside the function it returns 'green'?
If so you should probably just change:
return {Colors};
to be:
return Colors;

How to modify object property values while enumerating

An API call I'm making returns empty objects in lieu of null. Tedious doesn't like this, so before I save the API response I'm cleaning the data with the following function:
var object_to_return = input_object;
_.forOwn(object_to_return, function(key_value) {
if (_.isEmpty(key_value)) {
object_to_return[key_value] = null;
}
});
return object_to_return;
This isn't quite correct and I'm curious if anyone knows why and how I can fix it. I'm especially interested in the why and if I should bother with even returning a copy of the object (is it being passed in by reference, or...?)
_.forOwn exposes the key in the callback function; therefore, this worked:
module.exports.convertEmptyObjectsToNull = function(target_object) {
_.forOwn(target_object, function(property, key) {
if (_.isEmpty(property)) {
target_object[key] = null;
}
});
}
Also, as #apsillers mentioned, I wasn't doing much with my assignments, so this method just mutates the input object and doesn't attempt to clone it and return a copy.

Invoke a Function with the Correct Parameters from an Object in JavaScript

Let's say I have an object that looks like this:
{
'apple': 'nice',
'banana': 'decent',
'cherry': 'yuck',
}
and I have these two methods:
function eatItems(cherry, apple) { }
function throwItem(banana) { }
My two questions:
Is it possible for me to invoke eatItem and send the arguments in the correct order? Maybe something like:
eatItems.call(this, {'cherry': cherry, 'apple': apple});
What if I don't know what arguments eatItems receives, can I dynamically look up the names of the arguments for a function so I can know the order that I need to throw them in?
There's a way, indeed, and it involves calling toString on a function:
var source = eatItems.toString();
// => "function eatItems(cherry, apple) { }"
The next step is to parse the string you've got to get the names of the arguments:
var args = source.substring(source.indexOf("(") + 1, source.indexOf(")")),
argNames = /\S/.test(args) ? args.split(/\s*,\s*/) : [];
A few caveats:
This solution has been kept quite simple. It doesn't handle comments in the function definition.
Not every browser can correctly convert a function to a string (the PS3 browser comes to my mind), but they're a really small minority anyway.
I haven't tested it, but there may be some performance issues on slower machines and/or older browsers with large functions.
And, overall, this solution is more like an exercise. I wouldn't recommend taking this pattern in Javascript. Don't forget that some functions handle a variable number of arguments, and you won't find them listed in their definition. Rethink your code, and find a better way.
If I understand correctly you want extract the argument names from the function, and inject data from an object based on those names. This can be accomplished by converting the function to a string, extracting the arguments, and applying the function with those arguments:
function inject(data, f) {
var args = f.toString()
.match(/function\s*?\((.+?)\)/)
.pop()
.split(',')
.map(function(a){return data[a.trim()]})
return function() {
return f.apply(this, args)
}
}
var data = {
apple: 'nice',
banana: 'decent',
cherry: 'yuck',
}
var eat = inject(data, function(cherry, apple) {
console.log(cherry, apple)
})
eat() //=> yuck, nice
The obvious problem with this approach is that it is highly dependent on the variable names, so when you minify your code, the variables will get mangled and the function will stop working. This is a known problem in AngularJS, which uses something similar for their dependency injection.
This is often an XY problem, or an anti-pattern at the very least.

How can you pass a nested object property as an option, without passing a callback function?

I'm writing a simple jQuery plugin that will interface with a JSON object/array. I want to make that object array flexible enough so that people can pass their own to the plugin function. I'd like the user to be able to notify the plugin of how to get to two different values in the JSON object. For instance, let's say this is the JSON --
[
{
name: "Baseball Opening Day",
format: "jpg",
urls: {
small: "http://myurl.com/images/small/baseball.jpg",
big: "http://myurl.com/images/big/baseball.jpg"
}
},
// etc.
]
If the plugin "knows" it will be passed an array of image objects, but that those image objects might be formatted in a number of ways, how can we pass an option to tell it where to find various things? For example, if I want to display a caption, I will ask for a reference to the caption value. This one is fairly easy:
$("gallery").myPlugin({
references: {
caption: "name"
}
});
Inside the plugin, we can get to the value like this, once we are looping through the gathered image objects:
// Assuming the value passed in is called "options"...
var caption = image[options.references.caption];
That will be interpreted as image["name"] which is identical to image.name and works fine.
But how do you tell the plugin how to get to image.urls.small? You can't do this:
$("gallery").myPlugin({
references: {
caption: "name",
urlSmall: "urls.small"
}
});
because it would be interpreted as image["urls.small"] and return undefined.
Is there a way to pass this? Or at that point do I have to use a callback function like so:
$("gallery").myPlugin({
references: {
caption: "name",
urlSmall: function () {
return this.urls.small;
}
}
});
// Call it like this inside the plugin image loop
var urlSmall = options.references.urlSmall.call( image );
I know that is an option, but I'm wondering if there is a way to avoid forcing the user to write a callback function if they can pass a reference to the nested object property instead, somehow?
Thanks!
You just need a method that can split those property names. http://jsfiddle.net/mendesjuan/CUMgL/
var a = {
b: {
c: 2
}
};
getProp(a, 'b.c') // returns 2
function ​getProp(obj, propName)​ {
var parts = propName.split('.');
for (var i=0; i < parts.length; i++) {
obj = obj[parts[i]];
}
return obj;
}
Inside your plugin, you can do
// Assuming the value passed in is called "options"...
var caption = getProp( image, options.references.caption );
An ideal getProp would also work with getProp('a[b]'), but that is a bit harder, so you can implement it if you wish. Ext-JS has something called a Ext.data.reader.JsonReader that does exactly that, you can use it as inspiration http://docs.sencha.com/ext-js/4-0/source/Json2.html#Ext-data-reader-Json Look for method createAccessor

JS: using eval on a function while trying to pass an array as parameter, but it throws an error

i want to create a dynamic generated form using javascript, everything works fine, until i try to pass an array as parameter. When i do this, an error happens. Coulr anyone explain what this is?
Heres my code:
var loadFrm = function(component) {
for(nItem in component) {
var myComponent = "add" + firstToUpper(component[nItem].type);
var callComponent = myComponent + "(" + component[nItem].opt + ");";
eval(callComponent);
}
}
var json = [
{
type: "scale",
opt: {content: [{label: "male", value: "m"}, {label: "female", value: "f"}]}
}
];
loadFrm(json);
Edit Here's the error:
missing ] after element list
[Break on this error] addScale([object Object]);
If you use a debugger to look at the string callComponent, you'll probably find it looks something like this:
addScale([object Object])
...which isn't what you want. That's because you're effectively calling toString on your opt object, and the default toString on objects just looks like that. The eval error is because that's invalid syntax.
Generally speaking, any time you think you need to use eval, there's almost certainly a better answer. In this case, it looks like you're trying to call a function and pass in opt. Assuming these functions are "globals", you can do that like this:
var loadFrm = function(component) {
var nItem, functionName;
for (nItem = 0; nItem < component.length; ++nItem) {
functionName = "add" + firstToUpper(component[nItem].type);
window[functionName](component[nItem].opt);
}
}
Live example
Notes on the above:
Don't use for..in to loop through arrays unless you really know what you're doing. for..in does not enumerate the indexes of an array, it enumerates the properties of an object.
We look up the function by name using window[functionName]. This works because "globals" are actually properties of the window object, and you can look up properties using a string name for them using bracketed notation.
Having gotten the function via window[functionName], we just call it directly, passing in the object opt rather than a string form of it. I assume addScale expects to see an object.
I moved all of the vars to the top of the function because that's where they really are (details).
If you can, I'd recommend moving addScale and the related functions to their own object rather than putting them on window. The window namespace is already pretty crowded. Here's the live example modified to not add any symbols to window at all, instead putting the addScale function on an object called functions and using it from there.
Off-topic: The syntax var loadFrm = function(component) creates an anonymous function that it then assigns to a variable. This is used a lot, but unless you're creating different functions based on a condition, e.g.:
var f;
if (...) {
f = function() { ... };
}
else {
f = function() { ... };
}
...it's not actually useful. (If you are creating different functions based on a condition like that, then it's not only useful, it's necessary.) I recommend using named functions whenever possible, because a function with a name helps your tools help you by showing you the function name in error messages, call stacks, etc.
Off-topic 2: You have a variable called json, but FYI, it's not using JSON notation. It's using a combination of JavaScript array and object literal notation, which is a superset of JSON. You'll see a lot of people confused about this, I mention it because you said you're new and so it's worth nipping in the bud. :-) JSON is purely a notation. (A very useful one.)
Use this:
fn = eval(functionName);
fn(objParameter)

Categories