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;