JavaScript may not be the poster child of object-oriented programming languages, but that doesn’t mean you can’t take advantage of OO principles in your JS app. Plain Old JavaScript Objects, or POJOs, can be a lifesaver when working with complex datasets and databases. They’re easy to set up, and have many use cases such as normalizing data, handling faulty responses, and storing derived properties.

This post assumes basic knowledge of an object-oriented programming language, such as Java or C++. If you have not worked with classes and objects before, I recommend quickly reading up on OOP before reading this post further.

JavaScript 101: How to POJO

As with all things in JavaScript, there are multiple ways to create a JavaScript object. The one I prefer is defining a constructor function.

function CarData(make, model, year, condition) {
    this.make = make;
    this.model = model;
    this.year = year;
    this.condition = condition;
}
var myToyotaCorolla = new CarData('Toyota', 'Corolla', 2014, 'new');
console.log(myToyotaCorolla.make);  // -> 'Toyota'

Note the usage of the new keyword. This is the quintessential keyword that lets us create new instances of a “class”. Each class can have separate properties associated with it. For example, I can create a new CarData('BMW', '320i', 2015, 'new); and new CarData('Jeep', 'Cherokee', 2010, 'used');, which could for example represent the cars in my garage.

When to use POJOs

In general, POJOs are a lifesaver when dealing with any sort of structured data. POJOs and JSON especially go hand-in-hand; after all JSON does stand for JavaScript Object Notation. If your server responds with a different content type, like XML or even CSV, POJOs are still extremely useful, given a bit more data massaging.

You want to standardize data from a server or API

I was working with a REST API which returned data about the inventory of a car dealership. When querying the API, the response looked like this:

criteria: {
    category: ["NEW"],
    make: ["BMW"],
    model: ["320i", "325xi", "M4"],
    year: ["2015", "2014"]
},
active: "YES"

This data is fine to work with, but what if:

  • You want to rename the “category” key to “condition” because it makes more sense
  • You want to lowercase the make array for comparison purposes, and name it makes (after all, it’s an array)
  • You might be querying multiple APIs, some which use different terminology than others to label certain values

Let’s face it, APIs can be less than perfect with the format of data that they return. Furthermore, you don’t always have complete control over what kind of data, and how the data is sent to your client. This is where the power of client-side processing comes in. Consider the following POJO:

/**
 * @param serverData {object} raw response from server; see above
 */
function InventoryData(serverData) {
    this.conditions = serverData.criteria.category;
    this.makes = serverData.criteria.make;
    this.models = serverData.criteria.model;
    this.years = serverData.criteria.year;
    for (var i = 0; i < this.conditions.length; i++) {
        this.conditions[i] = this.conditions[i].toLocaleLowerCase();
    }
    for (var i = 0; i < this.makes.length; i++) {
        this.makes[i] = this.makes[i].toLocaleLowerCase();
    }
    for (var i = 0; i < this.models.length; i++) {
        this.models[i] = this.models[i].toLocaleLowerCase();
    }
    for (var i = 0; i < this.years.length; i++) {
        this.years[i] = parseInt(this.years[i], 10);
    }
    this.active = (serverData.active === "YES");
}

When this function is newed, it will do several things:

  • Flatten out the criteria and active keys, so that they are on the same hierarchy
  • Lowercase the make, model, and category arrays from the server, for easier case-insensitive comparison later on
  • Convert the year array from strings to integers
  • Convert the active value from a string to a representative boolean

In the function where I handle the API response, I would do the following:

function handleAPIResponse(dataJSON) {
    var inventoryData = new InventoryData(dataJSON);
    inventoryService.storeInventoryData(inventoryData);
}

Somewhere later I may have a function that takes a user’s input and sees if it matches any models in the dealership’s inventory. Since I’ve standardized all of the data from the API, it’s very easy to compare against my POJO.

function searchForModel(userInput) {
    var searchCriteria = userInput.toLocaleLowerCase();
    var inventoryData = inventoryService.getInventoryData();
    // This line is made possible by the fact that my POJO standardizes all models to be lowercase.
    // I can then use built-in indexOf to find what the user is looking for
    return inventoryData.models.indexOf(searchCriteria);
}

And we are just getting started! The POJO defines what we call a contract, an enforced set of rules between the the developer and the client. This POJO promises that — as long as the API does its job in returning data without error — all makes, models, and conditions will be lowercased, all years will strictly be integers, and the active field will be a boolean. This contract makes it much easier for the developer to work with the data later on.

If you are working with data returned in a format other than JSON, no fear! POJOs are still just as valid to use. For XML data, JavaScript has methods to deal with DOM manipulation. Plain text? Take a look at JavaScript’s many built in String methods.

You want to work with derived properties

In the previous example, I copied the data from the server response, more or less. But look closely, and notice that the server response contains more useful data that is not explicitly written. I determine that later on, I want to know how many models the dealership has. I can add a property to my POJO like so:

function InventoryData(serverData) {
    this.modelCount = serverData.model.length;
    // ... the rest
}

If I’m using a framework like Angular, I can display the data in the view like so:

{{inventoryData.modelCount}} model(s) available -- visit today!

modelCount is what I call a derived property, a property able to be obtained from analysis. With more complex datasets, storing derived properties once can improve the runtime when averaged over many accesses. In this example, accessing the length of an array may be a trivial operation, but more complex use cases such as counting letter occurrences in a DNA string can definitely benefit from introducing derived properties in a POJO.

You want to guard against undefined values

Some of the APIs I work with do not have a well-defined specification. When I query for data, some of their data may be or completely missing. Suppose we were querying a weather forecasting API, and got the following response:

{
    temp_now: 75,
    temp_hi: 89,
    wind_speed: [12],
    wind_chill: undefined
}

Normally, I would also expect a low temperature, a wind direction (as wind_speed[1] in the server response), and a wind_chill. Unfortunately, the server failed to provide the data. But I do not want to take its defaults. Instead, I define my own.

// different syntax, still a valid way to define a POJO
var WeatherData = function (serverData) {
    this.temp_now = serverData.temp_now || null;
    this.temp_hi = serverData.temp_hi || null;
    this.temp_lo = serverData.temp_lo || null;
    this.wind_speed = serverData.wind_speed[0] || 0;
    this.wind_direction = serverData.wind_direction[1] || undefined;
    this.wind_chill = serverData.wind_chill || 0;
}

For those not familiar with the notation var foo = A || B, foo gets A if A is not falsy; B otherwise.

Now, the temperatures are set to null when the API does not include them in the response, wind speed is assumed 0 if not provided, wind direction is (intentionally) set to undefined if not provided, and wind chill is assumed 0 if not provided. If we were to display, say, the wind speed and wind chill on a weather app, the value “0” looks a whole lot nicer than “null” or “undefined“. For temperature, we should not have a default of 0 since that is a valid temperature value. Instead, we may use something of the sort: displayedTemp = weatherData.temp_now || "Could not fetch temperature";.

You want to validate the data on the client side

Validating data is a huge part of computer security, and can guard against nefarious things such as Cross-Site Scripting (XSS) and SQL injection. It is also important if you want to keep an application from unexpectedly crashing due to some invalid data read in from 1000 lines ago. POJOs can handle invalid data in multiple ways: by standardizing it away as seen in the first use case, or by throwing exceptions.

Consider an electronic payment service, which stores users’, SSNs credit card numbers, and other validatable information. We know this service is usually reliable in its validation, but just as an extra layer of security, we want to validate client-side. On one occasion, the service responds with this dataset:

[{
    name: "John Q. Public",
    ccnum: 4441009065488719,
    ssn: 532146564
}, {
    name: "Not A. Scammer",
    ccnum: 1111111111111112,
    ssn: 1234567890
}]

Our POJO for handling the data could look like this.

// Also demonstrates the use of multiple POJOs in conjunction with one another
var PaymentDataList = function(serverData) {
    this.paymentData = [];
    for (var i = 0; i < serverData.length; i++) {
        this.paymentData.push(new PaymentData(serverData[i]));
    }
}
var PaymentData = function(pData) {
    // Do some validation first
    if (pData.ccnum) {
        // the Luhn algorithm is used as one means of validating credit card numbers
        if (checkAgainstLuhnAlgorithm(pData.ccnum)) {
            // for the sake of simplicity, assume valid
            this.ccnum = pData.ccnum;
        } else {
            throw new Exception("Invalid credit card number");
        }
    } else {
        throw new Exception("No credit card number given!");
    }
    if (isValidSSN(pData.ssn)) {
        this.ssn = pData.ssn;
    } else {
        throw new Exception("Invalid Social Security Number!");
    }
}

In the code to handle the response from the service:

function handleServiceResponse(serviceData) {
    var paymentDataList;
    try {
        paymentDataList = new PaymentDataList(serviceData);
    } catch (e) {
        paymentDataList.length = 0;
        console.err(e);
        // terminate the program or do whatever is necessary to keep running
    }
}

Now your application is immune to scammers trying to use invalid credit cards for payment. In the real world, the developer may create different kinds of Exceptions, or defer from throwing exceptions at all and attempt to continue the program, with all invalid data being sanitized.

Using POJOs dynamically

So far, I have only discussed using POJOs as storage objects. They are set once, and accessed many times thereafter with no modification to the original contents. Writing data to a POJO more than once is a completely valid use case, especially if your application allows users to update data. JavaScript POJOs can indeed have functions.

Recall my second example (derived properties in a POJO), where I derived a property:

function InventoryData(data) {
    this.makes = data.make;
    this.makesCount = this.make.length;
}

The problem here is, if I add a make to my POJO, makesCount does not update properly. It is set once and never touched again.

var invData = new InventoryData(serverData);
expect(invData.makesCount).toEqual(3);  // assume true
invData.makes.push("Mazda");
expect(invData.makesCount).toEqual(4);  // fails!

On the other hand, we can turn makesCount into a function, as in the following setup:

function InventoryData(data) {
    this.makes = data.make;
    this.getMakesCount = function () {
        return this.makes.length;
    }
}

Then you would call invData.getMakesCount(). A small change, but now you have a dynamic accessor in your POJO.

What about mutators and setters?

If you want to take your POJOs to the next level of OO abstraction, then you can introduce mutators/setters alongside your getter methods. In order for setters to make sense in your POJO, you would have to declare the appropriate fields as private. Otherwise clients would simply set the variable directly, e.g. this.makes = [...] as opposed to this.setMakes([...]). Douglas Crockford discusses private members in detail, and I encourage you to read up on it.

A word about POJOs, regular Objects, and Jasmine

I was bamboozled a few weeks ago when writing a Jasmine test that involved the use of POJOs. Writing out a JavaScript object and constructing one will cause toEqual() to fail! Consider the simple case:

var GNum = function (value) {
   this.value = value;
};
// test it
it('Will fail in 1.2, but not earlier versions', function () {
    var expected = {
        value: 1
    };
    var actual = new GNum(1);
    expect(actual).toEqual(expected);  // fail!
});

The reason why is that Jasmine will compare the constructors of the two objects actual and expected. They are different in this case. You can read more on my StackOverflow post.

Wrap-up

Introducing POJOs turned out to be a game-changer in our app. I hope this post will help you learn to love the abstraction and simplification POJOs can bring to your app as well. In short, use POJOs to

  • Standardize data
  • Store derived properties
  • Guard against undefined values
  • Validate data
  • Abstract getters and setters for a dataset

There are of course many other use cases, but this post should cover the basics. If you have an interesting use case, let me know in the comments!

Published by Geoffrey Liu

A software engineer by trade and a classical musician at heart. Currently a software engineer at Groupon getting into iOS mobile development. Recently graduated from the University of Washington, with a degree in Computer Science and a minor in Music. Web development has been my passion for many years. I am also greatly interested in UI/UX design, teaching, cooking, biking, and collecting posters.

2 thoughts on “Object-Oriented JavaScript: Using POJOs for good

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.