Home Manual Reference Source Test Repository
Manual » Usage

Basic Usage

The two key classes of this library are Serializer and SerializerInterface. To use it, you just have to write your own serializers (simple class implementing SerializerInterface). Once done, create a Serializer instance serializer to aggregate all your serializers. You will then be able to use serializer to (de-)serialize any kind of data supported by the registered serializers.

Example: For some reason, we are fetching data where instead of having booleans true and false in our JSON response, we have integers 0 or 1. Let us write a simple BooleanSerializer that will be responsible for deserializing them into proper booleans.

// app/Serializer/BooleanSerializer.js
import SerializerFactory from 'serializerjs';

export default class BooleanSerializer extends SerializerFactory.SerializerInterface {
    /**
     * @inheritDoc
     */
    supportsSerialize(data, format = null) {
        return false;
    }

    /**
     * @inheritDoc
     *
     * @param {number|boolean|null} data
     *
     * @return {boolean}
     */
    deserialize(data, className, format = null, context = null) {
        if ('boolean' === typeof data || null === data) {
            return data;
        }

        return Boolean(data);
    }

    /**
     * @inheritDoc
     */
    supportsDeserialize(data, className, format = null) {
        if ('boolean' !== className) {
            return false;
        }

        if ('boolean' === typeof data || null === data) {
            return true;
        }

        if (0 === data || 1 === data) {
            return true;
        }

        return false;
    }
}

Then create the serializer service with all the serializers you which to register. In our case we only have BooleanSerializer:

// app/Serializer/Serializer.js
import SerializerFactory from 'serializerjs';
import BooleanSerializer from './BooleanSerializer';

export default SerializerFactory(new Map([
    ['BooleanSerializer', booleanSerializer],
]));

You can then use it like so:

// app/index.js
import serializer from './Serializer/Serializer';

serializer.supportsDeserialize(1, 'boolean')); // => true
serializer.deserialize(1, 'boolean'); // => true

serializer.supportsSerialize(true); // => true
serializer.serialize(true); // => true

Reusable serializers: SerializerAware

Depending of the data (de-)serialized, you may need to use an existing serializer. For example, provided with have the following user class:

// app/Model/User.js

export default class User {
    /**
     * @param {string}   firstname
     * @param {string}   lastname
     * @param {?boolean} gender    True for female, false for male and null for unknown
     */
    constructor(firstname, lastname, type, gender) {
        this.firstname = firstname;
        this.lastname = lastname;
        this._gender = gender;
    }

    getFullname() {
        return `${this.firstname} ${this.lastname}`;
    }

    isMale() {
        return false === this._gender;
    }

    isFemale() {
        return true === this._gender;
    }

    isGenderKnown() {
        return null === this._gender;
    }
}

We can see that User#gender is a boolean. We are receiving the following JSON from an API:

{
  "FirstName": "John",
  "LastName": "Doe",
  "gender": 0
}

As you can see, the gender value is not an boolean like we expected besides having the property names that do not match with our User object. As a result we could do a serializer for User: UserSerializer:

// app/Serializer/UserSerializer.js

import SerializationError from 'serializerjs'.SerializationError;
import SerializerInterface from 'serializerjs'.SerializerInterface;
import User from './../Model/User';

export default class UserSerializer extends SerializerInterface {
    /**
     * @inheritDoc
     *
     * @param {User} user
     *
     * @return {!object}
     */
    serialize(user, format, context) {
        return user;
    }

    /**
     * @inheritDoc
     */
    supportsSerialize(data, format = null) {
        return 'object' === typeof data && null !== data && data instanceof User;
    }

    /**
     * @inheritDoc
     *
     * @param {!object} userObject
     *
     * @return {User}
     */
    deserialize(userObject, ...args) {
        const firstname = this._deserializeString(userObject, 'firstname');
        const lastname = this._deserializeString(userObject, 'lastname');
        const gender = this._deserializeGender(userObject);

        return new User(firstname, lastname, gender);
    }

    /**
     * @param {!Object} object
     * @param {string}  property
     *
     * @return {string}
     * @throw {SerializationError}
     * @private
     */
    _deserializeString(object, property) {
        if (object.hasOwnProperty(property) && 'string' === typeof object[property]) {
            return object[property];
        }

        throw new SerializationError(`Could not serialize object#${property}`);
    }

    /**
     * @param {!Object} object
     *
     * @return {boolean}
     * @throw {SerializationError}
     */
    _deserializeGender(object) {
        // TODO
    }

    /**
     * @inheritDoc
     */
    supportsDeserialize(data, className, format = null) {
        return 'User' === className && 'object' === typeof data && null !== data;
    }
}

Now there is only deserializing the gender left. We could do it easily, but why bother? We already did it in BooleanSerializer!

Instead of re-doing this work, we instead use existing serializers. For that, UserSerializer should be made "serializer aware", i.e. should extend SerializerAware instead of implementing SerializerInterface. By doing is so, our UserSerializer will inherit a [_serializer][3] property which will have a reference to your Serialzier at runtime. As a result, we can have the following code:

// app/Serializer/UserSerializer.js

- import SerializerInterface from 'serializerjs'.SerializerInterface;
+ import SerializerAware from 'serializerjs'.SerializerAware;

- export default class UserSerializer extends SerializerInterface {
+ export default class UserSerializer extends SerializerAware {

/**
 * @param {!Object} object
 *
 * @return {boolean}
 * @throw {SerializationError}
 */
_deserializeGender(object) {
-    // TODO
+    if (object.hasOwnProperty('gender') && this._serializer.supportsDeserialize(object.gender, 'boolean')) {
+        return this._serializer.deserialize(object.gender, 'boolean');
+    }
+
+    throw new SerializationError(`Could not serialize object#gender`);
}

In other words, SerializerAware allow you to quickly access to other serializer.

Serializer format & context

When checking the SerializerInterface interface, you well notice the format and context parameters. format is expected to be a string with the format name. For example json, json-ld, website, legacy-api, etc. It is should give you an information of the data schema during the deserialization, or the schema in which the data will be serialized during serialization.

For example with the BooleanSerializer, we could need to serialize booleans again (in integer values) before sending the data back to the server. In which case, we could easily do something like this:

// app/Serializer/BooleanSerializer.js

supportsSerialize(data, format = null) {
-    return false;
+    return null === format || 'legacy-api' === format;
}

+ /**
+  * @inheritDoc
+  *
+  * @param {?boolean} booleanValue
+  * @param {?string}  format Either null or 'legacy-api'
+  *
+  * @return {boolean|number|null}
+  */
+ serialize(booleanValue, format, context) {
+     if ('legacy-api' === format) {
+         return Number(booleanValue);
+     }
+
+     return booleanValue;
+ }

Depending of the format, we could need to pass additional parameters for the serialization. That is where comes context. This property can be pretty much everything you want: object, array, scalar value etc.

Framework integration

The integration in any framework should be very straightforward. For example, to register it in Angular, you can simply register a serializer service:

// app/HSerializerModule.js

import SerializerFactory from 'serializerjs';

import BooleanSerializer from './Serializer/BooleanSerializer';
import StringSerializer from './Serializer/StringSerializer';
import TypeSerializer from './Serializer/TypeSerializer';
import UserSerializer from './Serializer/UserSerializer';

const hserializerModule = angular
    .module('haircvt.serializerModule', [])
    .factory('serializer', () => {
        const booleanSerializer = new BooleanSerializer();
        const stringSerializer = new StringSerializer();
        const typeSerializer = new TypeSerializer();
        const userSerializer = new UserSerializer();

        return SerializerFactory(new Map([
            ['BooleanSerializer', booleanSerializer],
            ['StringSerializer', stringSerializer],
            ['TypeSerializer', typeSerializer],
            ['UserSerializer', userSerializer],
        ]));
    })
;

export default hserializerModule;

Back to the Table of Content