clock menu more-arrow no yes mobile

Filed under:

Managing complex object inputs with wrapper classes in JavaScript

A quick tip for handling raw input in your software library.

A series of blocks of different colors representing nested code in a code editor.

We can’t always get what we want. This is especially true for engineers building scalable, revenue-impacting software.

Whether we’re parsing ad slot configuration or generating the creative for a custom ad, we often find ourselves accepting a large JSON object as an object input.

Sometimes, this means passing the object around to multiple methods. The object might become mutated over time or take on different responsibilities. However, we lose the context of the initial input data as we pass it around to different methods, modules, or even files. It can get pretty hairy pretty quickly!

Let’s start with a simple example. This is a theoretical script that accepts a JSON payload of experiment configurations required for us to run an A/B test in our library. The primary method, runExperiment, is responsible for calling other methods to filter out inactive experiments and to finally act on a chosen experiment:

See the Pen Wrapper Classes: Before by Joshua P. Larson (@jplhomer) on CodePen.

Notice how the raw JavaScript configuration object is passed between multiple functions In the getActiveExperiments function, we also have to manually convert a timestamp to a JavaScript Date object in order to make a comparison. Finally, in the pickExperiment function, we are referencing multiple properties from the raw object using “snake_case.”

If this object were more complex, or if we were dealing with multiple modules spread across a larger software library, we could begin to lose our place when reading through each function. It’s important to know the shape of our input data and what properties we have available.

Refactoring into a wrapper class

Let’s refactor what we have above. This time, we’ll use a wrapper class to give our input data some structure.

See the Pen Wrapper Classes: After by Joshua P. Larson (@jplhomer) on CodePen.

We start by creating a new JavaScript class called Experiment. The constructor of this class accepts our raw JSON input and assigns it to a “private” (we’re using vanilla JavaScript here, so no real support for visibility) instance variable called _data.

Next, we create a few getters on the class like start, id and variantId. These provide a convenient access pattern for data without having to reach deep into a raw object, and they allow us to transition to “camelCase” which better fits our coding style.

Additionally, notice that the start getter casts the value of the start date to an actual JavaScript Date object. This helps define the shape of our wrapper class and ensures any consumers that the variable they’re trying to access will already be a date.

The wrapper class pattern allows us to create new properties that didn’t already exist on the object, like isActive and key. These helpers let us perform calculations on existing object in the form of a getter. This reminds me a lot of Vue’s computed properties — though it’s important to note that getters will re-run their logic each time they are called, where Vue’s computed properties are memoized.

Finally, we add a method to our wrapper class called doSomethingWithThisExperiment. We can encapsulate any methods like this which are sole responsibilities of the Experiment object by placing them directly in the class.

While this wrapper class is a little more work up front, we are able to simplify our business logic below. We’re able to remove an entire function and make the code more readable.

Level up: Refactoring with TypeScript

For some extra fun, we can convert this code into TypeScript instead of JavaScript with only a few small changes.

By defining an interface, we can more safely parse the raw JSON object into the class:

See the Pen Wrapper Classes: TypeScript by Joshua P. Larson (@jplhomer) on CodePen.

TypeScript will throw an error during compilation if we try to access a non-existent property on the input object. It also allows us to use private instance variables, which means no more underscore on our property name!

A word of caution: TypeScript validates types at compile time, but it outputs standard JavaScript. This means a change to the shape of the raw JSON data in production would not be caught by TypeScript. Your mileage may vary.

Wrapper classes: the good and the bad

To recap, the wrapper class pattern seems to shine in a few ways:

  • Types: As software developers, we can control the shape of our wrapper class and the data types it contains instead of adhering to the shape of JSON input we’ve received from another service.
  • Convenience: By using getters and encapsulated methods, we have direct access to the data we need without having to use dot notation or utility functions.
  • Documentation: This may be a personal preference, but it’s easier to reference a wrapper class within a project than to print out an entire JSON object example in a comment to describe what data is available. Plus, modern editors like VS Code will provide autocomplete functionality to consumers of the wrapper class.
  • Immutability: By providing your data as getters, your wrapper acts as an immutable object. This means that — unless you provide an explicit setter — an error will be thrown if a consumer tries to modify a property on your wrapper class. This helps prevent unintended consequences and side effects.
  • Future-proof: Once we’ve defined an interface in the form of a wrapper class, we can continue to iterate on the implementation of the class without worrying that we’ll break anything.

There are some trade-offs, though. Here are some things to consider before diving in:

  • Cost: While JavaScript classes are supported in most modern browsers, you may need to transpile them using something like Babel in order to support older browsers.
  • Testing: If you’re writing unit tests that used to accept raw JSON data as input, you’ll need to remember to instantiate your wrapper class each time and pass that to the methods instead. This might feel cumbersome or unnecessary while testing.
  • Speed: If you’re quickly prototyping something, you might feel impeded by wrapper classes. They might be a better choice if you’re not in the middle of a code spike.

That’s all I have on wrapper classes. I hope you find this pattern useful!