🥣 ts_serialize
A zero dependency library for serializing data.
ts_serialize
can help you with:
- Converting
camelCase
class members tosnake_case
JSON properties for use with a REST API - Excluding internal fields from REST API payloads
- Converting data types to an internal format, for example:
Date
's
Supported Serialized Types:
Installing
ts_serialize
supports both deno
and node
.
Deno
export
what you need from https://deno.land/x/ts_serialize/mod.ts
The examples in this README pull from our
develop
branch. You would want to "pin" to a particular version which is compatible with the version of Deno you are using and has a fixed set of APIs you would expect.https://deno.land/x/
supports using git tags in the URL to direct you at a particular version. So to use version 2.0.0 ofts_serialize
you would want to importhttps://deno.land/x/ts_serialize@v2.0.0/mod.ts
.
Node
Install with npm i @gamebridgeai/ts_serialize
or
yarn add @gamebridgeai/ts_serialize
Serializable
and SerializeProperty()
Using To quickly get started extend Serializable
with your class and add the
property decorator
SerializeProperty()
to any class property you want in the serialization
process.
import { Serializable, SerializeProperty } from "./mod.ts";
class MyClass extends Serializable {
@SerializeProperty()
public myProperty = "Hello world!";
}
Serializable Methods
Serializable
will add 5 methods. Each method has an implementable interface if
you wish to provide your own functionality.
fromJSON
Takes one argument, the JSON
string
orJSONObject
to deserialize creating an object.fromJSON
will perform providedtsTransformKey
operations and strategy value transformations.toJSON
Converts the model to a JSON
string
and will perform providedtsTransformKey
operations and strategy value transformations.clone
Returns a new reference of the object with all properties cloned, an optional parameter can be provided which is a
Partial<T>
whereT
is your class.tsSerialize
Converts the model to "Plain old Javascript object" with any provided
tsTransformKey
or value transformationstsTransformKey
Called against every key and has one parameter, the key to transform. The return value is a string. The default is to return the original parameter name. Key transformations will be inherited by children classes. Children classes can also
override
their parenttsTransformKey
function.
With this in mind we can write a more complex example. We'll make a base
class
that provides a key transformation then add child classes.
import { Serializable, SerializeProperty, TransformKey } from "./mod.ts";
abstract class Base extends Serializable implements TransformKey {
public tsTransformKey(key: string): string {
return `__${key}__`;
}
}
class Parent extends Base {
@SerializeProperty()
public parentProperty = "Hello world!";
}
class ChildOne extends Parent {
@SerializeProperty()
public childOneProperty = "Ahoy hoy world!";
}
class ChildTwo extends Parent implements TransformKey {
@SerializeProperty()
public childTwoProperty = "Good Day world!";
@SerializeProperty("myCustomName")
public childTwoPropertyTwo = "Howdy world!";
public tsTransformKey(key: string): string {
return `--${key}--`;
}
}
Passing a string or a function that returns a string as an argument to
SerializeProperty()
causes the property to use that name as the key when
serialized. The function has one parameter, the key
as string and should
return a string.
Inherited classes override the key when serializing. If you override a property any value used for that key will be overridden by the child value. With collisions the child always overrides the parent
SerializeProperty()
options
SerializeProperty()
also excepts an optional options object with the
properties
serializedKey
(Optional)
{string | ToSerializedKeyStrategy}
A string value or function that has one parameter, the property key, and returns a string. The resulting value is used as the key in the serialized objecttoJSONStrategy
(Optional)
{ToJSONStrategy}
A function that has one parameter, the class property value and returns a value to be used when serialized asJSON
fromJSONStrategy
(Optional)
{FromJSONStrategy}
A function that has one parameter, theJSON
property value and returns a value to be used when serialized as a class
Strategies
Strategies are functions or a composed list of functions to execute on the values when serializing or deserializing. The functions take one argument which is the value to process.
import { Serializable, SerializeProperty } from "./mod.ts";
const fromJSONStrategy = (v: string): BigInt => BigInt(v);
const toJSONStrategy = (v: BigInt): string => v.toString();
class Test extends Serializable {
@SerializeProperty({
serializedKey: "big_int",
fromJSONStrategy,
toJSONStrategy,
})
bigInt!: BigInt;
}
toJSONStrategy
and fromJSONStrategy
can use composeStrategy
to build out
strategies with multiple functions.
import { composeStrategy, Serializable, SerializeProperty } from "./mod.ts";
const addWord = (word: string) => (v: string) => `${v} ${word}`;
const shout = (v: string) => `${v}!!!`;
class Test extends Serializable {
@SerializeProperty({
fromJSONStrategy: composeStrategy(addWord("World"), shout),
})
property!: string;
}
Dates
Dates can use the fromJSONStrategy
to revive a serialized string into a Date
object. ts_serialize
provides a iso8601Date
function to parse ISO Dates.
import { iso8601Date, Serializable, SerializeProperty } from "./mod.ts";
class Test extends Serializable {
@SerializeProperty({
fromJSONStrategy: iso8601Date(),
})
date!: Date;
}
createDateStrategy()
can be used to make a reviving date strategy. Pass a
regex to make your own. The example below uses a yyyy-mm-dd
format to
construct a Date
import { createDateStrategy, Serializable, SerializeProperty } from "./mod.ts";
class Test extends Serializable {
@SerializeProperty({
fromJSONStrategy: createDateStrategy(/^(\d{4})-(\d{2})-(\d{2})$/),
})
date!: Date;
}
@SerializeProperty
decorator
Short cutting the While @SerializeProperty
is handy with to and from JSON strategies, it can
still be verbose to declare the strategies for each property. You can define
your own decorator functions to wrap @SerializeProperty
and provide the
toJSONStrategy
and fromJSONStrategy
. An example short cut is providing a
type
to use with toSerializable
. getNewSerializable
is provided to allow a
raw serializable type or a function that returns a constructed serializable type
enabling constructor arguments:
import {
getNewSerializable,
Serializable,
SerializeProperty,
toSerializable,
} from "./mod.ts";
function DeserializeAs(
type: unknown,
): PropertyDecorator {
return SerializeProperty({
fromJSONStrategy: toSerializable(() => getNewSerializable(type)),
});
}
class A extends Serializable {
@SerializeProperty("property_a")
public property = "";
}
class B extends Serializable {
@DeserializeAs(A)
public property = new A();
public otherProperty = "";
constructor({ otherProperty = "" }: Partial<B> = {}) {
super();
this.otherProperty = otherProperty;
}
}
class C extends Serializable {
@DeserializeAs(() => new B({ otherProperty: "From Class C" }))
public property = new B();
}
Polymorphism
The @PolymorphicResolver
and @PolymorphicSwitch
decorators can be used to
cleanly handle deserializing abstract types into their constituent
implementations.
PolymorphicSwitch()
The @PolymorphicSwitch()
decorator is a quick way to serialize simple
polymorphic types based on the properties of a child class.
Note that @PolymorphicSwitch()
can only be applied to child classes
deserializing from their direct parent class.
Properties decorated with @PolymorphicSwitch()
must also be serializable
properties. The from JSON strategy and associated serialized key of that
property will be used when comparing the value.
import {
polymorphicClassFromJSON,
PolymorphicSwitch,
Serializable,
SerializeProperty,
} from "./mod.ts";
enum Colour {
RED = "RED",
BLUE = "BLUE",
}
abstract class MyColourClass extends Serializable {}
class MyRedClass extends MyColourClass {
@SerializeProperty()
@PolymorphicSwitch(() => new MyRedClass(), Colour.RED)
public colour = Colour.RED;
@SerializeProperty()
public crimson = false;
}
const redClass = polymorphicClassFromJSON(
MyColourClass,
`{"colour":"RED","crimson":true}`,
);
You can also provide a test function instead of a value to check if the value for the annotated property satisfies a more complex condition:
import {
polymorphicClassFromJSON,
PolymorphicSwitch,
Serializable,
SerializeProperty,
} from "./mod.ts";
abstract class Currency extends Serializable {}
class DollarCurrency extends Currency {
@SerializeProperty()
@PolymorphicSwitch(() => new DollarCurrency(), "$")
public currencySymbol = "$";
@SerializeProperty()
public amount = 0;
}
class OtherCurrency extends Currency {
@SerializeProperty()
@PolymorphicSwitch(
() => new OtherCurrency(),
(value) => value !== "$",
)
public currencySymbol = "";
@SerializeProperty()
public amount = 0;
}
const currencyClass = polymorphicClassFromJSON(
Currency,
`{"currencySymbol":"£","amount":300}`,
);
Multiple @PolymorphicSwitch
annotations can be applied to a single class, if
necessary
import {
polymorphicClassFromJSON,
PolymorphicSwitch,
Serializable,
SerializeProperty,
} from "./mod.ts";
abstract class MyAbstractClass extends Serializable {}
class MyClass extends MyAbstractClass {
@SerializeProperty()
@PolymorphicSwitch(() => new MyClass(), "$")
@PolymorphicSwitch(() => new MyClass(), "dollar")
@PolymorphicSwitch(
() => new MyClass(),
(value) => typeof value === "string" && value.includes("dollars"),
)
public myProperty = "$";
@SerializeProperty()
public amount = 0;
}
const myClass1 = polymorphicClassFromJSON(
MyAbstractClass,
`{"myProperty":"300 dollars"}`,
);
const myClass2 = polymorphicClassFromJSON(
MyAbstractClass,
`{"myProperty":"dollar"}`,
);
Polymorphic Resolver
The following example shows how the @PolymorphicResolver
decorator can be used
to directly determine the type of an abstract class implementor, which will then
be used when deserializing JSON input.
import {
polymorphicClassFromJSON,
PolymorphicResolver,
Serializable,
SerializeProperty,
} from "./mod.ts";
enum Colour {
RED = "RED",
BLUE = "BLUE",
}
abstract class MyColourClass extends Serializable {
@SerializeProperty()
public colour?: Colour;
@PolymorphicResolver()
public static resolvePolymorphic(input: string): MyColourClass {
const colourClass = new PolymorphicColourClass().fromJSON(input);
switch (colourClass.colour) {
case Colour.RED:
return new MyRedClass();
case Colour.BLUE:
return new MyBlueClass();
default:
throw new Error(`Unknown Colour ${colourClass.colour}`);
}
}
}
class PolymorphicColourClass extends MyColourClass {}
class MyRedClass extends MyColourClass {
@SerializeProperty()
private crimson = false;
public isCrimson(): boolean {
return this.crimson;
}
}
class MyBlueClass extends MyColourClass {
@SerializeProperty()
private aqua = false;
public isAqua(): boolean {
return this.aqua;
}
}
const redClass = polymorphicClassFromJSON(
MyColourClass,
`{"colour":"RED","crimson":true}`,
);
Built With
- Deno 🦕
Contributing
We have provided resources to help you request a new feature or report and fix a bug.
- CONTRIBUTING.md - for guidelines when requesting a feature or reporting a bug or opening a pull request
- DEVELOPMENT.md - for instructions on setting up the environment and running the test suite
- CODE_OF_CONDUCT.md - for community guidelines
Versioning
We use SemVer for versioning.
Authors
- Scott Hardy - Initial work - @hardy925 🐸
- Chris Dufour - Initial work - @ChrisDufourMB 🍕 🐱 👑
See also the list of contributors who participated in this project.
License
This project is licensed under the MIT License - see the LICENSE file for details
Acknowledgments
- Our colleagues at MindBridge for discussion and project planning
- Parsing Dates with JSON for knowledge
- OAK Server as a project structure example