true-json v1.0.3
TrueJSON
Respectful JSON serialization & deserialization for JavaScript
comment: <> ([(https://img.shields.io/npm/dw/true-json.svg)](https://www.npmjs.com/package/true-json))
Table of contents
- What's TrueJSON?
- Installation
- Basic usage
- Using JSON5 and other JSON alternatives
- Built-in adapters
- Writing your own adapter
- Contributing
What's TrueJSON?
TrueJSON is a JSON serialization and deserialization library for JavaScript and TypeScript. It helps with the serialization and
deserialization of complex types such as Date
, Set
, Map
, and many others.
What's wrong with JSON.stringify()
and JSON.parse()
?
Imagine the following JavaScript object:
const originalObject = {
date: new Date(),
set: new Set([1, 2, 3])
};
const serializedObject = JSON.stringify(originalObject);
const deserializedObject = JSON.parse(serializedObject);
If you check the value of the serializedObject
variable, you'll see the following JSON structure:
{
"date": "2021-05-08T06:39:29.215Z",
"set": {}
}
As you can see, your set elements haven't been serialized as you would expect. Moreover, if you check the value of the
deserializedObject
variable, you'll see the following object:
Now the date
property is a string
, and the set
property is an empty object, which, probably, isn't the desired behaviour.
TrueJSON to the rescue
Using TrueJSON, you can create a JsonConverter
that knows how to serialize and deserialize your object without loosing any
information. This can be done by using JSON adapters, which are components that know how to adapt some data types to
jsonable values. Let's see an example:
import {JsonConverter, JsonAdapters} from 'true-json';
// Create a converter using adapters to describe your object's type
const customJsonConverter = new JsonConverter(JsonAdapters.object({
date: JsonAdapters.isoDate(),
set: JsonAdapters.set()
}));
const originalObject = {
date: new Date(),
set: new Set([1, 2, 3])
};
const serializedObject = customJsonConverter.stringify(originalObject);
const deserializedObject = customJsonConverter.parse(serializedObject);
If you check the value of the serializedObject
variable, now you'll see the following JSON structure:
{
"date": "2021-05-08T06:45:45.000Z",
"set": [
1,
2,
3
]
}
As you can see, now your Set
has been serialized as a JSON array, preserving its values in the JSON structure. In addition, if
you check the value of the deserializedObject
variable, you'll see the following object:
As you can see, both the date
property and the set
property have been deserialized to Date
and Set
objects respectively.
In addition, TrueJSON will perform some validations (i.e. type checks) before serializing or deserializing, throwing errors if the received input doesn't match the expected structure.
Features
- Serialization and deserialization of complex data types - see Built-in adapters section.
- Serialization and deserialization of custom data types - see Writing your own adapter section.
- Input validation on serialization and deserialization.
- Configurable JSON serializer - see Using JSON5 and other JSON alternatives section.
Installation
Using NPM (module)
Install the latest stable version:
npm install --save true-json
Then you can import TrueJSON objects in your modules:
import {JsonConverter, JsonAdapters} from 'true-json';
Using <script>
tag (standalone)
You can download the latest version from here. Then, you can use it as any other JavaScript file:
<script src="true-json.js"></script>
Or, if you prefer, you can use any of the following CDN repositories:
<!-- Unpkg -->
<script src="https://unpkg.com/true-json@1.0.1"></script>
<!-- JsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/true-json@1.0.1"></script>
The script will create a global TrueJSON
object, which contains all the exported objects.
Basic usage
Using import
(module)
import {JsonConverter, JsonAdapters} from 'true-json';
const user = {
name: 'John Doe',
birthDate: new Date('1970-01-01'),
bestScoreByGame: new Map([
['Minesweeper', 118],
['Donkey Kong', 35500],
['Super Mario Bros.', 183250],
])
};
const userJsonConverter = new JsonConverter(JsonAdapters.object({
birthDate: JsonAdapters.isoDate(),
bestScoreByGame: JsonAdapters.mapAsRecord()
}));
const userAsJson = userJsonConverter.stringify(user);
console.log(userAsJson);
Using TrueJSON
object (standalone)
You can access any object just by doing TrueJSON.[ObjectName]
:
const user = {
name: 'John Doe',
birthDate: new Date('1970-01-01'),
bestScoreByGame: new Map([
['Minesweeper', 118],
['Donkey Kong', 35500],
['Super Mario Bros.', 183250],
])
};
const userJsonConverter = new TrueJSON.JsonConverter(TrueJSON.JsonAdapters.object({
birthDate: TrueJSON.JsonAdapters.isoDate(),
bestScoreByGame: TrueJSON.JsonAdapters.mapAsRecord()
}));
const userAsJson = userJsonConverter.stringify(user);
console.log(userAsJson);
You can also use ES6 destructuring assignment in order to imitate module imports:
const {JsonConverter, JsonAdapters} = TrueJSON;
const user = {
name: 'John Doe',
birthDate: new Date('1970-01-01'),
bestScoreByGame: new Map([
['Minesweeper', 118],
['Donkey Kong', 35500],
['Super Mario Bros.', 183250],
])
};
const userJsonConverter = new JsonConverter(JsonAdapters.object({
birthDate: JsonAdapters.isoDate(),
bestScoreByGame: JsonAdapters.mapAsRecord()
}));
const userAsJson = userJsonConverter.stringify(user);
console.log(userAsJson);
Using JSON5 or other JSON alternatives
You can configure TrueJSON's JsonConverter
to use any custom JSON implementation, such as JSON5. The only requirement is that
the custom JSON implementation should have the same parse()
and stringify()
methods as the standard one.
Let's see an example using json5
's NPM package:
import json5 from 'json5';
import {JsonConverter, JsonAdapters} from 'true-json';
const user = {
name: 'John Doe',
birthDate: new Date('1970-01-01'),
bestScoreByGame: new Map([
['Minesweeper', 118],
['Donkey Kong', 35500],
['Super Mario Bros.', 183250],
])
};
const userJsonAdapter = JsonAdapters.object({
birthDate: JsonAdapters.isoDate(),
bestScoreByGame: JsonAdapters.mapAsRecord()
});
// The second argument of JsonConverter's constructor
// allows you to pass a custom JSON implementation.
// When no value is passed, the standard JSON object is used.
const userJsonConverter = new JsonConverter(userJsonAdapter, json5);
const userAsJson = userJsonConverter.stringify(user);
console.log(userAsJson);
Built-in adapters
In this section, we will cover the build-in adapters that TrueJSON provides to you.
isoDate()
This adapter converts any Date
object into his ISO textual representation (see
Date.prototype.toISOString()
docs) and vice versa:
const adapter = JsonAdapters.isoDate();
console.log(adapter.adaptToJson(new Date(0)));
console.log(adapter.recoverFromJson('1970-01-01T00:00:00.000Z'));
Output:
"1970-01-01T00:00:00.000Z"
Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) }
dateTimestamp()
This adapter converts any Date
object into his number representation in milliseconds (see
Date.prototype.getTime()
docs) and vice versa:
const adapter = JsonAdapters.dateTimestamp();
console.log(adapter.adaptToJson(new Date(0)));
console.log(adapter.recoverFromJson(0));
0
Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) }
array(elementAdapter)
Using this adapter you can specify how the elements of the array should be adapted:
const adapter = JsonAdapters.array(JsonAdapters.dateTimestamp());
console.log(adapter.adaptToJson([
new Date(0),
new Date(1620458583563)
]));
console.log(adapter.recoverFromJson([
0,
1620458583563
]));
Output:
[0, 1620458583563]
[
Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) },
Date { Sat May 08 2021 07:23:03 GMT+0000 (Coordinated Universal Time) }
]
set([elementAdapter])
By default, JavaScript sets are serialized as an empty object. Using this adapter allows you to serialize them in the same way that arrays are serialized:
const adapter = JsonAdapters.set();
console.log(adapter.adaptToJson(new Set([1, 2, 3])));
console.log(adapter.recoverFromJson([1, 2, 3]));
Output:
[1, 2, 3]
Set { 1, 2, 3 }
You can also specify an adapter to be used for mapping the elements of the set:
const adapter = JsonAdapters.set(JsonAdapters.dateTimestamp());
console.log(adapter.adaptToJson(new Set([new Date(0), new Date(1620458583563)])));
console.log(adapter.recoverFromJson([0, 1620458583563]));
Output:
[0, 1620458583563]
Set {
Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) },
Date { Sat May 08 2021 07:23:03 GMT+0000 (Coordinated Universal Time) }
}
Notice that calling JsonAdapters.set()
without any element adapter is equivalent to
JsonAdapters.set(JsonAdapters.identity())
.
record(valueAdapter)
A record is a JavaScript plain object consisting of key-value pairs. In that sense, it's a similar to a Map
(a.k.a.
hashtable or dictionary in other programming languages), but its keys are always strings*.
* JavaScript allows to use the symbol
type as a key also, but TrueJSON expects records to be in the form
{ string: any }
(for TypeScript users: Record<string, any>
).
The record adapter receives an adapter that will be applied to each of the record values, just as the array adapter does:
const adapter = JsonAdapters.record(JsonAdapters.dateTimestamp());
console.log(adapter.adaptToJson({
start: new Date(0),
end: new Date(1620458583563)
}));
console.log(adapter.recoverFromJson({
start: 0,
end: 1620458583563
}));
Output:
{
"start": 0,
"end": 1620458583563
}
{
"start": Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) },
"end": Date { Sat May 08 2021 07:23:03 GMT+0000 (Coordinated Universal Time) }
}
Configuration options
The record adapter allows to modify its default behaviour using the following configuration options:
Property | Type | Default value | Description |
---|---|---|---|
strictPlainObjectCheck | boolean | false | When true , it will throw an error if the input value of the adaptToJson() method is not a plain object. |
mapAsEntries([config])
By default, JavaScript maps are serialized as an empty object. Using this adapter allows you to serialize them as an array of entries (see Map.prototype.entries()):
const map = new Map();
map.set('number', 42);
map.set('string', 'hello world');
map.set('array', [1, 2, 3]);
const adapter = JsonAdapters.mapAsEntries();
console.log(adapter.adaptToJson(map));
console.log(adapter.recoverFromJson([
['number', 42],
['string', 'hello world'],
['array', [1, 2, 3]]
]));
Output:
[
["number", 42],
["string", "hello world"],
["array", [1, 2, 3]]
]
Map {
"number" => 42,
"string" => "hello world",
"array" => [1, 2, 3]
}
As map's keys and values can be any type of object, you can also specify a keyAdapter
and a valueAdapter
:
const map = new Map();
map.set(new Date(0), new Set([1, 2, 3]));
map.set(new Date(1620458583563), new Set([4, 5, 6]));
const adapter = JsonAdapters.mapAsEntries({
keyAdapter: JsonAdapters.dateTimestamp(),
valueAdapter: JsonAdapters.set(),
});
console.log(adapter.adaptToJson(map));
console.log(adapter.recoverFromJson([
[0, [1, 2, 3]],
[1620458583563, [4, 5, 6]]
]));
Output:
[
[0, [1, 2, 3]],
[1620458583563, [4, 5, 6]]
]
Map {
Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) } => Set { 1, 2, 3 },
Date { Sat May 08 2021 07:23:03 GMT+0000 (Coordinated Universal Time) } => Set { 4, 5, 6 }
}
Notice that calling JsonAdapters.mapAsEntries()
without key and value adapters is equivalent to:
JsonAdapters.mapAsEntries({
keyAdapter: JsonAdapters.identity(),
valueAdapter: JsonAdapters.identity()
});
mapAsRecord([config])
By default, JavaScript maps are serialized as an empty object. Using this adapter allows you to serialize them as a plain JS object (a.k.a. record):
const map = new Map();
map.set('number', 42);
map.set('string', 'hello world');
map.set('array', [1, 2, 3]);
const adapter = JsonAdapters.mapAsRecord();
console.log(adapter.adaptToJson(map));
console.log(adapter.recoverFromJson({
number: 42,
string: 'hello world',
array: [1, 2, 3]
}));
Output:
{
"number": 42,
"string": "hello world",
"array": [1, 2, 3]
}
Map {
"number" => 42,
"string" => "hello world",
"array" => [1, 2, 3]
}
As map's keys and values can be any type of object, you can also specify a keyAdapter
and a valueAdapter
:
const map = new Map();
map.set(new Date(0), new Set([1, 2, 3]));
map.set(new Date(1620458583563), new Set([4, 5, 6]));
const adapter = JsonAdapters.mapAsRecord({
keyAdapter: JsonAdapters.isoDate(),
valueAdapter: JsonAdapters.set(),
});
console.log(adapter.adaptToJson(map));
console.log(adapter.recoverFromJson({
"1970-01-01T00:00:00.000Z": [1, 2, 3],
"2021-05-08T07:23:03.563Z": [4, 5, 6]
}));
Output:
{
"1970-01-01T00:00:00.000Z": [1, 2, 3],
"2021-05-08T07:23:03.563Z": [4, 5, 6]
}
Map {
Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) } => Set { 1, 2, 3 },
Date { Sat May 08 2021 07:23:03 GMT+0000 (Coordinated Universal Time) } => Set { 4, 5, 6 }
}
Notice that calling JsonAdapters.mapAsRecord()
without key and value adapters is equivalent to:
JsonAdapters.mapAsRecord({
keyAdapter: JsonAdapters.identity(),
valueAdapter: JsonAdapters.identity()
});
object(propertyAdapters[, config])
This adapter allows you to serialize & deserialize any plain JS object, specifying different adapters for each of its properties:
const film = {
name: 'Harry Potter and the Deathly Hallows - Part 2',
releaseDate: new Date('2011-07-15'),
mainCharacters: new Set(['Harry Potter', 'Hermione Granger', 'Ron Weasley'])
};
const adapter = JsonAdapters.object({
releaseDate: JsonAdapters.isoDate(),
mainCharacters: JsonAdapters.set()
});
console.log(adapter.adaptToJson(film));
console.log(adapter.recoverFromJson({
name: 'Harry Potter and the Deathly Hallows - Part 2',
releaseDate: '2011-07-15T00:00:00.000Z',
mainCharacters: ['Harry Potter', 'Hermione Granger', 'Ron Weasley']
}));
Output:
{
"name": "Harry Potter and the Deathly Hallows - Part 2",
"releaseDate": "2011-07-15T00:00:00.000Z",
"mainCharacters": ["Harry Potter", "Hermione Granger", "Ron Weasley"]
}
{
"name": "Harry Potter and the Deathly Hallows - Part 2",
"releaseDate": Date { Fri Jul 15 2011 00:00:00 GMT+0000 (Coordinated Universal Time) },
"mainCharacters": Set { "Harry Potter", "Hermione Granger", "Ron Weasley" }
}
By default, any unmapped property will be adapted using the identity adapter.
Configuration options
The object adapter allows to modify its default behaviour using the following configuration options:
Property | Type | Default value | Description |
---|---|---|---|
omitUnmappedProperties | boolean | false | When true , all unmapped properties won't be present on the resultant object. |
omittedProperties | string[] | [] | Allows to specify which properties should be omitted manually. |
strictPlainObjectCheck | boolean | false | When true , it will throw an error if the input value of the adaptToJson() method is not a plain object. |
Example using omittedProperties
option:
const adapter = JsonAdapters.object(
{
birthDate: JsonAdapters.dateTimestamp()
},
{
omittedProperties: ['age']
}
);
console.log(adapter.adaptToJson({
name: 'John Doe',
birthDate: new Date('1970-01-01'),
age: 51
}));
console.log(adapter.recoverFromJson({
name: 'John Doe',
birthDate: 0,
age: 51
}));
Output:
{
"name": "John Doe",
"birthDate": 0
}
{
"name": "John Doe",
"birthDate": Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) }
}
byKey(keyValuePairs)
This adapter allows you to serialize a value using its corresponding key of the provided key-value pairs object. This is specially useful when working with enumerated values:
const ScalingStrategies = {
DEFAULT: new DefaultScalingStrategy(),
FAST: new FastScalingStrategy(),
SMOOTH: new SmoothScalingStrategy()
};
const adapter = JsonAdapters.byKey(ScalingStrategies);
console.log(adapter.adaptToJson(ScalingStrategies.FAST));
console.log(adapter.recoverFromJson('SMOOTH'));
Output:
"FAST"
SmoothScalingStrategy { }
If any unknown value* is passed to adaptToJson()
or recoverFromJson()
methods, an error is thrown. If you don't want
this to happen, you can use
byKeyLenient(keyValuePairs\[, fallbackKey\])
method.
byKeyLenient(keyValuePairs[, fallbackKey])
This is very similar to byKey(keyValuePairs)
's adapter. The main difference is the case where the
passed value is not present in the keyValuePairs
object. While the byKey(keyValuePairs)
adapter will
throw an error, this adapter will return undefined
:
const ScalingStrategies = {
DEFAULT: new DefaultScalingStrategy(),
FAST: new FastScalingStrategy(),
SMOOTH: new SmoothScalingStrategy()
};
const adapter = JsonAdapters.byKeyLenient(ScalingStrategies);
console.log(adapter.adaptToJson(new UnknownScalingStrategy()));
console.log(adapter.recoverFromJson('UNKNOWN'));
Output:
undefined
undefined
Due to this, undefined
value is also a valid input for both adaptToJson()
and recoverFromJson()
methods:
const ScalingStrategies = {
DEFAULT: new DefaultScalingStrategy(),
FAST: new FastScalingStrategy(),
SMOOTH: new SmoothScalingStrategy()
};
const adapter = JsonAdapters.byKeyLenient(ScalingStrategies);
console.log(adapter.adaptToJson(undefined));
console.log(adapter.recoverFromJson(undefined));
Output:
undefined
undefined
Alternatively, you can pass a fallback key to use when an unknown object or key is passed to the adapter:
const ScalingStrategies = {
DEFAULT: new DefaultScalingStrategy(),
FAST: new FastScalingStrategy(),
SMOOTH: new SmoothScalingStrategy()
};
const adapter = JsonAdapters.byKeyLenient(ScalingStrategies, 'DEFAULT');
console.log(adapter.adaptToJson(new UnknownScalingStrategy()));
console.log(adapter.recoverFromJson('UNKNOWN'));
Output:
"DEFAULT"
DefaultScalingStrategy { }
Identity adapters
Identity adapters take their name from the concept of identity function. Those adapters doesn't really adapt its input value, but they're still able to perform some validations that will ensure the received JSON has the expected format. The following sections will cover the different identity adapters provided by TrueJSON out-of-the-box.
identity([validator])
The identity adapter takes its name from the concept of identity function. It just returns the same value it receives:
const adapter = JsonAdapters.identity();
console.log(adapter.adaptToJson(3));
console.log(adapter.recoverFromJson(3));
Output:
3
3
By default, it doesn't perform any validations, but it accepts to pass a validator function, allowing you to implement any validation logic that you may need:
const probabilityAdapter = JsonAdapters.identity(input => {
if (typeof input !== 'number' || !Number.isFinite()) {
throw new TypeError('input value is not a finite number');
}
if(input < 0 || input > 1) {
throw new Error('input value is out of [0, 1] range');
}
});
console.log(probabilityAdapter.adaptToJson('A text'));
console.log(probabilityAdapter.adaptToJson(3));
console.log(probabilityAdapter.adaptToJson(0.3));
console.log(probabilityAdapter.recoverFromJson(Infinity));
console.log(probabilityAdapter.recoverFromJson(-1));
console.log(probabilityAdapter.recoverFromJson(0.95));
Output:
TypeError: input value is not a finite number
Error: input value is out of [0, 1] range
0.3
TypeError: input value is not a finite number
Error: input value is out of [0, 1] range
0.95
Additionally, in the following sections you'll find some other identity adapters that perform some of the most-common type validations.
stringIdentity()
The string identity adapter simply returns the same value it receives, throwing an error if the value is not a string:
const stringIdentityAdapter = JsonAdapters.stringIdentity();
console.log(stringIdentityAdapter.adaptToJson('A text'));
console.log(stringIdentityAdapter.adaptToJson(3));
console.log(stringIdentityAdapter.recoverFromJson('Another text'));
console.log(stringIdentityAdapter.recoverFromJson(['an', 'array']));
Output:
"A text"
TypeError: input value is not a string
"Another text"
TypeError: input value is not a string
numberIdentity()
The number identity adapter simply returns the same value it receives, throwing an error if the value is not a finite
number (this means that non-finite values like Infinite
, -Infinite
or NaN
are not valid):
const integerIdentityAdapter = JsonAdapters.numberIdentity();
console.log(integerIdentityAdapter.adaptToJson(1234));
console.log(integerIdentityAdapter.adaptToJson(Infinity));
console.log(integerIdentityAdapter.recoverFromJson(-5.7));
console.log(integerIdentityAdapter.recoverFromJson({an: 'object'}));
Output:
1234
TypeError: input value is not a finite number
-5.7
TypeError: input value is not a finite number
integerIdentity()
The integer identity adapter simply returns the same value it receives, throwing an error if the value is not an integer
number (this means that decimal numbers and non-finite values like 12.34
, Infinite
, -Infinite
or NaN
are not
valid):
const integerIdentityAdapter = JsonAdapters.integerIdentity();
console.log(integerIdentityAdapter.adaptToJson(1234));
console.log(integerIdentityAdapter.adaptToJson(12.34));
console.log(integerIdentityAdapter.recoverFromJson(-5));
console.log(integerIdentityAdapter.recoverFromJson(NaN));
Output:
1234
TypeError: input value is not an integer
-5
TypeError: input value is not an integer
booleanIdentity()
The boolean identity adapter simply returns the same value it receives, throwing an error if the value is not a boolean:
const booleanIdentityAdapter = JsonAdapters.booleanIdentity();
console.log(booleanIdentityAdapter.adaptToJson(true));
console.log(booleanIdentityAdapter.adaptToJson('true'));
console.log(booleanIdentityAdapter.recoverFromJson(false));
console.log(booleanIdentityAdapter.recoverFromJson(0));
Output:
true
TypeError: input value is not a boolean
false
TypeError: input value is not a boolean
Handling nullish values
Extracted from MDN:
In JavaScript, a nullish value is the value which is either
null
orundefined
.
Adapters discussed in previous sections are not designed taking nullish values into account. If you try to use them
for serializing or deserializing null
or undefined
values, they could throw an error or return an unexpected value.
Let's take a look to the behaviour of the set adapter:
const standardSetAdapter = JsonAdapters.set();
console.log(standardSetAdapter.adaptToJson(new Set([1, 2, 3])));
console.log(standardSetAdapter.adaptToJson(null));
console.log(standardSetAdapter.adaptToJson(undefined));
console.log(standardSetAdapter.recoverFromJson([1, 2, 3]));
console.log(standardSetAdapter.recoverFromJson(null));
console.log(standardSetAdapter.recoverFromJson(undefined));
Output:
[1, 2, 3]
TypeError: o is not iterable
TypeError: o is not iterable
Set { 1, 2, 3 }
TypeError: Cannot read properties of null (reading 'map')
TypeError: Cannot read properties of undefined (reading 'map')
The same applies when you write your own adapters. If you don't write your adapter having nullish values in mind, it may not work as expected when receiving one.
Fortunately, TrueJSON allows to wrap any existing adapter using a proxy adapter that handles null
and undefined
values:
nullishAware(adapter)
Wraps an existing adapter using a proxy that handles both null
and undefined
values. This proxy will return the
received value when it's a nullish value; otherwise it will call the real adapter:
const nullishAwareSetAdapter = JsonAdapters.set();
console.log(nullishAwareSetAdapter.adaptToJson(new Set([1, 2, 3])));
console.log(nullishAwareSetAdapter.adaptToJson(null));
console.log(nullishAwareSetAdapter.adaptToJson(undefined));
console.log(nullishAwareSetAdapter.recoverFromJson([1, 2, 3]));
console.log(nullishAwareSetAdapter.recoverFromJson(null));
console.log(nullishAwareSetAdapter.recoverFromJson(undefined));
Output:
[1, 2, 3]
null
undefined
Set { 1, 2, 3 }
null
undefined
nullAware(adapter)
Wraps an existing adapter using a proxy that handles only null
values. This proxy will return null
when receiving
the null
value; otherwise it will call the real adapter:
const nullAwareSetAdapter = JsonAdapters.nullAware(JsonAdapters.set());
console.log(nullAwareSetAdapter.adaptToJson(new Set([1, 2, 3])));
console.log(nullAwareSetAdapter.adaptToJson(null));
console.log(nullAwareSetAdapter.adaptToJson(undefined));
console.log(nullAwareSetAdapter.recoverFromJson([1, 2, 3]));
console.log(nullAwareSetAdapter.recoverFromJson(null));
console.log(nullAwareSetAdapter.recoverFromJson(undefined));
Output:
[1, 2, 3]
null
TypeError: o is not iterable
Set { 1, 2, 3 }
null
TypeError: Cannot read properties of undefined (reading 'map')
Notice an error is thrown when using the undefined
value.
undefinedAware(adapter)
Wraps an existing adapter using a proxy that handles only undefined
values. This proxy will return undefined
when
receiving the undefined
value; otherwise it will call the real adapter:
const undefinedAwareSetAdapter = JsonAdapters.undefinedAware(JsonAdapters.set());
console.log(undefinedAwareSetAdapter.adaptToJson(new Set([1, 2, 3])));
console.log(undefinedAwareSetAdapter.adaptToJson(null));
console.log(undefinedAwareSetAdapter.adaptToJson(undefined));
console.log(undefinedAwareSetAdapter.recoverFromJson([1, 2, 3]));
console.log(undefinedAwareSetAdapter.recoverFromJson(null));
console.log(undefinedAwareSetAdapter.recoverFromJson(undefined));
Output:
[1, 2, 3]
TypeError: o is not iterable
undefined
Set { 1, 2, 3 }
TypeError: Cannot read properties of null (reading 'map')
undefined
Notice an error is thrown when using the null
value.
Writing your own adapter
You can write your own adapter using the JsonAdapters.custom()
method:
const dateToArrayAdapter = JsonAdapters.custom({
adaptToJson(date) {
// Perform some validations
if (!(date instanceof Date)) {
throw new TypeError('input value is not a date');
}
// Adapt the Date object to array
return [
date.getFullYear(),
date.getMonth(),
date.getDate()
];
},
recoverFromJson(array) {
// Perform some validations
if (!Array.isArray(array)) {
throw new TypeError('input value is not an array');
}
if (array.length !== 3) {
throw new TypeError('input value has not the expected length');
}
// Recover the Date object from the deserialized array
const [
year,
month,
date
] = array;
return new Date(year, month, date);
}
});
Then, you can use it as any other adapter:
const objectAdapter = JsonAdapters.object({
birthDate: dateToArrayAdapter
});
console.log(objectAdapter.adaptToJson({
name: 'John Doe',
birthDate: new Date('1970-01-01')
}));
console.log(objectAdapter.recoverFromJson({
name: 'John Doe',
birthDate: [1970, 0, 1]
}));
Output:
{
"name": "John Doe",
"birthDate": [1970, 0, 1]
}
{
"name": "John Doe",
"birthDate": Date { Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time) }
}
Contributing
This is a library maintained by one person, so any bug report, suggestion, pull request, or any other kind of feedback will be really appreciated :slightly_smiling_face:
Please contribute using GitHub Flow. Create a branch from the develop
one, add commits, and open a pull request.
Please note we have a code of conduct, please follow it in all your interactions with the project.
If you want to get in touch with the author, you can contact me through LinkedIn or email.
1 year ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago