4

Suppose I have a json object in which I record the number of visitors to my site, grouped by browser / version.

let data = {
    browsers: {
        chrome: {
           43 : 13, 
           44 : 11
        }, 
        firefox: {
           27: 9
        }
    }
}

To increment a particular browser, I need to check if several keys exist, and if not, create them.

let uap = UAParser(request.headers['user-agent']);

if (typeof uap.browser !== 'undefined') {

    if (typeof data.browsers === 'undefined') 
        data.browsers = {}

    if (typeof data.browsers[uap.browser.name] === 'undefined')
        data.browsers[uap.browser.name] = {}

    if (typeof data.browsers[uap.browser.name][uap.browser.version] === 'undefined')
        data.browsers[uap.browser.name][uap.browser.version] = 0

    data.browsers[uap.browser.name][uap.browser.version] += 1;
}

The deeper my data structure the crazier things get.

It feels like there must be a neater way to do this in javascript. There's always a neater way. Can anyone enlighten me here?

4
  • 1
    There is already an excellent library for such operations - lodash. It has get method which does what you need: _.set(object, 'a[0].b.c', 4); Commented Aug 28, 2017 at 11:14
  • Use Object.prototype.hasOwnProperty() instead of typeof... Commented Aug 28, 2017 at 11:18
  • @DenisGiffeler that seems like a longer way of doing things. I'm trying to shorten my code Commented Aug 28, 2017 at 11:41
  • @alexmac I want to avoid using lodash or any other library for this - looking for neater vanilla JS Commented Aug 28, 2017 at 11:41

3 Answers 3

6

This shorter code should do the trick:

if (uap.browser) { // typeof is pretty much redundant for object properties.
    const name = uap.browsers.name; // Variables for readability.
    const version = uap.browser.version;

    // Default value if the property does not exist.
    const browsers = data.browsers = data.browsers || {}; 
    const browser = browsers[name] = browsers[name] || {};
    browser[version] = browser[version] || 0;

    // Finally, increment the value.
    browser[version]++;
}

Note that you were using === where you should've been using = (in === {}).

Let's explain this line:

const browsers = data.browsers = data.browsers || {};

The last part: data.browsers = data.browsers || {} sets data.browsers to be itself if it exists. If it doesn't yet, it's set to be a new empty object.
Then, that whole value gets assigned to browsers, for ease of access.

Now, shorter code shouldn't be top priority, but in cases like this, you can make the code a lot more readable.

Sign up to request clarification or add additional context in comments.

2 Comments

oops. yes, I see the === where it should be = - thanks for that
@roryok: np. I've added a little disclaimer about "shorter code". In the end, you want to go for readability, which may not necessarily mean "shorter" (the name/version are a good example there). Thanks for accepting this answer!
2

You can give up the if statements and do it like this:

uap.browser = uap.browser || {}

essentially it does the same as the if only much shorter

Comments

1

Here's a very clean and generic solution using Proxy() I have a second solution which is standard ECMAscript 5 if you don't need it so cleanly or need it less browser dependant.

var handler = {
    get: function (target, property) {
        if (property !== "toJSON" && target[property] === undefined) {
            target[property] = new Proxy ({}, handler);
        }
        return target[property];
    }
}
var jsonProxy = new Proxy ({}, handler);

jsonProxy.non.existing.property = 5;
jsonProxy.another.property.that.doesnt.exist = 2;
jsonProxy["you"]["can"]["also"]["use"]["strings"] = 20;

console.log (JSON.stringify (jsonProxy));

You can do the same thing with classes but with a more verbose syntax:

var DynamicJSON = function () {};
DynamicJSON.prototype.get = function (property) {
    if (this[property] === undefined) {
        this[property] = new DynamicJSON ();
    }
    return this[property];
};
DynamicJSON.prototype.set = function (property, value) {
    this[property] = value;
};

var jsonClass = new DynamicJSON ();
jsonClass.get("non").get("existing").set("property", 5);
jsonClass.get("you").get("have").get("to").get("use").set("strings", 20);

console.log (JSON.stringify (jsonClass));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.