A Look Into Ember Data Transforms

Last reviewed on November 23, 2015

Ember Data has a feature called transforms that allow you to transform values before they are set on a model or sent back to the server. A transform has two functions: serialize and deserialize. Deserialization converts a value to a format that the client expects. Serialization does the reverse and converts a value to the format expected by the backend. If you've been working with Ember Data, then you have already been using transforms and may not have known it. The built in transforms include:

  • string
  • number
  • boolean
  • date

These transforms are used when model attributes are declared using DS.attr(). For example:

app/models/person.js
export default DS.Model.extend({
  name: DS.attr('string'),
  age: DS.attr('number'),
  admin: DS.attr('boolean'),
  lastLogin: DS.attr('date'),
  phone: DS.attr(),
});

When the model is created, the attributes are transformed to the types specified in the corresponding DS.attr() call. Behind the scenes, each of these DS.attr() calls map to a specific transform class that extends from DS.Transform. If you don't pass anything to DS.attr(), like the phone attribute in the model above, the value will be passed through:

DS.attr() Transform Class
DS.attr('boolean') DS.BooleanTransform
DS.attr('number') DS.NumberTransform
DS.attr('string') DS.StringTransform
DS.attr('date') DS.DateTransform

So what's going on behind each of these Transform classes? Let's take a look at the Ember Data source code.

When you search for NumberTransform, you'll see it points to this:

ember$data$lib$transforms$base$$default.extend({
  deserialize: function (serialized) {
    var transformed;

    if (ember$data$lib$transforms$number$$empty(serialized)) {
      return null;
    } else {
      transformed = Number(serialized);

      return ember$data$lib$transforms$number$$isNumber(transformed)
        ? transformed
        : null;
    }
  },

  serialize: function (deserialized) {
    var transformed;

    if (ember$data$lib$transforms$number$$empty(deserialized)) {
      return null;
    } else {
      transformed = Number(deserialized);

      return ember$data$lib$transforms$number$$isNumber(transformed)
        ? transformed
        : null;
    }
  },
});

If you remove the long prefix ember$data$lib$transforms$number$$, the class reads a little easier:

ember$data$lib$transforms$base$$default.extend({
  deserialize: function (serialized) {
    var transformed;

    if (empty(serialized)) {
      return null;
    } else {
      transformed = Number(serialized);

      return isNumber(transformed) ? transformed : null;
    }
  },

  serialize: function (deserialized) {
    var transformed;

    if (empty(deserialized)) {
      return null;
    } else {
      transformed = Number(deserialized);

      return isNumber(transformed) ? transformed : null;
    }
  },
});

You can see that it uses the Number function to convert the value back and forth. If the attribute is not a number, null is returned. StringTransform is similar and pretty self explanatory, using the String function.

ember$data$lib$transforms$base$$default.extend({
  deserialize: function (serialized) {
    return none(serialized) ? null : String(serialized);
  },
  serialize: function (deserialized) {
    return none(deserialized) ? null : String(deserialized);
  },
});

I found the BooleanTransform interesting because it deserializes value types other than Boolean:

  • The strings "true" or "t" in any casing, or "1" will coerce to true, and false otherwise
  • The number 1 will coerce to true, and false otherwise
  • Anything other than boolean, string, or number will coerce to false

Here is the implementation:

ember$data$lib$transforms$base$$default.extend({
  deserialize: function (serialized) {
    var type = typeof serialized;

    if (type === 'boolean') {
      return serialized;
    } else if (type === 'string') {
      return serialized.match(/^true$|^t$|^1$/i) !== null;
    } else if (type === 'number') {
      return serialized === 1;
    } else {
      return false;
    }
  },

  serialize: function (deserialized) {
    return Boolean(deserialized);
  },
});

And lastly, the DateTransform:

ember$data$lib$transforms$base$$default.extend({
  deserialize: function (serialized) {
    var type = typeof serialized;

    if (type === 'string') {
      return new Date(Ember.Date.parse(serialized));
    } else if (type === 'number') {
      return new Date(serialized);
    } else if (serialized === null || serialized === undefined) {
      // if the value is null return null
      // if the value is not present in the data return undefined
      return serialized;
    } else {
      return null;
    }
  },

  serialize: function (date) {
    if (date instanceof Date) {
      return date.toISOString();
    } else {
      return null;
    }
  },
});

The DateTransform is interesting because it also deserializes a few different values. If the date is a string, it should be in a format recognized by Date.parse(). According to MDN, that date format should be either RFC2822 or ISO 8601.

The ISO 8601 format looks like this: YYYY-MM-DDTHH:mm:ss.sssZ. More information on that can be found here.

Because Date.parse() in some browsers does not support simplified ISO 8601 dates, like Safari 5-, IE 8-, Firefox 3.6-, Ember uses a shim.

Alternatively, a number can be passed that represents the number of milliseconds since 1 January 1970 00:00:00 UTC (Unix Epoch). Otherwise, null or undefined is returned.

The DateTransform serialization process converts it to the ISO 8601 string format if the model property is an instance of Date. Otherwise null is sent.

Creating Custom Transforms

You can also create custom transforms. Here is a simple transform that converts values in cents (maybe the database stores everything in cents) to US dollars.

ember g transform dollars
import DS from 'ember-data';

export default DS.Transform.extend({
  deserialize: function (serialized) {
    return serialized / 100; // returns dollars
  },
  serialize: function (deserialized) {
    return deserialized * 100; // returns cents
  },
});

Then, simply use DS.attr('dollars') in the model:

app/models/person.js
export default DS.Model.extend({
  name: DS.attr('string'),
  age: DS.attr('number'),
  admin: DS.attr('boolean'),
  lastLogin: DS.attr('date'),
  phone: DS.attr(),
  spent: DS.attr('dollars'),
});

What custom transforms have you made? Thanks for reading!


Pro Ember Data cover
Pro Ember Data available now!

Are you struggling with Ember Data?

In my new book Pro Ember Data, you will learn how to work with Ember Data efficiently, from APIs, adapters, and serializers to polymorphic relationships, using your existing JavaScript and Ember knowledge. This book will teach you how to adapt Ember Data to fit your custom API.

What You'll Learn

  • Review the differences between normalization and serialization
  • Understand how the built-in adapters and serializers in Ember Data
  • Understand how the built-in adapters and serializers in Ember Data work
  • Customize adapters and serializers to consume any API and write them from scratch
  • Handle API errors in Ember Data
  • Work with the Reddit API using Ember Data
  • Learn how to use polymorphic relationships
Check out Pro Ember Data on Amazon using my affiliate link
You can also find Pro Ember Data on Apress.

I've been enjoying @iamdtang's Pro Ember Data book. It's to the point and has lots of great examples. My favorite part was about error handling. This book is perfect after you've exhausted the official guides.

Ilya Radchenko

Product Developer at Applied Geographics

Ilya Radchenko

Pro Ember Data is such an approachable book @iamdtang! Good job. Loving the book so far! Thank you!

Lenora Porter

Software Engineer at Heroku

Lenora Porter

Great to see all those topics in which I struggled when I started working on Ember. Nice job, will check it out :)

AbulAsar S

Fullstack Developer

AbulAsar S

I haven't read David's new book, but his previous Ember Data book helped me a lot when I was starting out with Ember, so I am sure this book is a must if you're working with Ember Data!

Kenneth Larsen

Ember Learning Core Team member

Kenneth Larsen

I bought Ember Data in the Wild back in 2016 and found it really helpful. Looking forward to reading this.

@EmberLinks

Ember related news by Chris Masters

@EmberLinks