@gamebridgeai/ts_serialize v2.1.0
🥣 ts_serialize
A zero dependency library for serializing data.
ts_serialize can help you with:
- Converting
camelCaseclass members tosnake_caseJSON 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
developbranch. 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_serializeyou 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
Using Serializable and SerializeProperty()
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.
fromJSONTakes one argument, the JSON
stringorJSONObjectto deserialize creating an object.fromJSONwill perform providedtsTransformKeyoperations and strategy value transformations.toJSONConverts the model to a JSON
stringand will perform providedtsTransformKeyoperations and strategy value transformations.cloneReturns a new reference of the object with all properties cloned, an optional parameter can be provided which is a
Partial<T>whereTis your class.tsSerializeConverts the model to "Plain old Javascript object" with any provided
tsTransformKeyor value transformationstsTransformKeyCalled 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
overridetheir parenttsTransformKeyfunction.
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 override 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 override 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 asJSONfromJSONStrategy(Optional)
{FromJSONStrategy}A function that has one parameter, theJSONproperty 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,
})
public 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),
})
public 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(),
})
public 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})$/),
})
public date!: Date;
}Short cutting the @SerializeProperty decorator
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
5 months ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago