https://github.com/gamebridgeai/ts_serialize
A zero dependency library for serializing data
https://github.com/gamebridgeai/ts_serialize
browser deno javascript json node serialization typescript
Last synced: 2 months ago
JSON representation
A zero dependency library for serializing data
- Host: GitHub
- URL: https://github.com/gamebridgeai/ts_serialize
- Owner: GameBridgeAI
- License: mit
- Created: 2020-06-08T16:36:38.000Z (about 6 years ago)
- Default Branch: develop
- Last Pushed: 2025-06-04T16:09:46.000Z (about 1 year ago)
- Last Synced: 2025-07-04T04:38:42.270Z (12 months ago)
- Topics: browser, deno, javascript, json, node, serialization, typescript
- Language: TypeScript
- Homepage: https://deno.land/x/ts_serialize
- Size: 743 KB
- Stars: 17
- Watchers: 5
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: .github/CONTRIBUTING.md
- License: LICENSE
- Code of conduct: .github/CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
Awesome Lists containing this project
README
# 🥣 ts_serialize
[](https://github.com/GameBridgeAI/ts_serialize/workflows/tests/badge.svg)
[](https://github.com/GameBridgeAI/ts_serialize/workflows/release/badge.svg)
[](https://doc.deno.land/https/deno.land/x/ts_serialize/mod.ts)
[](https://opensource.org/licenses/MIT)
A zero dependency library for serializing data.
`ts_serialize` can help you with:
1. Converting `camelCase` class members to `snake_case` JSON properties for use
with a REST API
2. Excluding internal fields from REST API payloads
3. Converting data types to an internal format, for example: `Date`'s
Supported Serialized Types:
- [`JSON`](https://www.json.org/json-en.html)
## 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 of `ts_serialize` you would want to import
> `https://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](https://www.typescriptlang.org/docs/handbook/decorators.html#property-decorators)
`SerializeProperty()` to any class property you want in the serialization
process.
```typescript
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` or `JSONObject` to deserialize creating
an object. `fromJSON` will perform provided `tsTransformKey` operations and
strategy value transformations.
- `toJSON`
Converts the model to a JSON `string` and will perform provided
`tsTransformKey` 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` where `T` is your class.
- `tsSerialize`
Converts the model to "Plain old Javascript object" with any provided
`tsTransformKey` or value transformations
- `tsTransformKey`
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 parent `tsTransformKey` 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.
```typescript
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 object
- `toJSONStrategy`
(Optional) `{ToJSONStrategy}` A function that has one parameter, the class
property value and returns a value to be used when serialized as `JSON`
- `fromJSONStrategy`
(Optional) `{FromJSONStrategy}` A function that has one parameter, the `JSON`
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.
```typescript
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.
```typescript
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.
```typescript
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`
```typescript
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:
```typescript
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 = {}) {
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.
```typescript
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:
```typescript
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
```typescript
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.
```typescript
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](http://deno.land) 🦕
## Contributing
We have provided resources to help you request a new feature or report and fix a
bug.
- [CONTRIBUTING.md](./.github/CONTRIBUTING.md) - for guidelines when requesting
a feature or reporting a bug or opening a pull request
- [DEVELOPMENT.md](./.github/DEVELOPMENT.md) - for instructions on setting up
the environment and running the test suite
- [CODE_OF_CONDUCT.md](./.github/CODE_OF_CONDUCT.md) - for community guidelines
## Versioning
We use [SemVer](http://semver.org/) for versioning.
## Authors
- **Scott Hardy** - _Initial work_ - [@hardy925](https://github.com/hardy925) 🐸
- **Chris Dufour** - _Initial work_ -
[@ChrisDufourMB](https://github.com/ChrisDufourMB) 🍕 🐱 👑
See also the list of [contributors](./.github/CONTRIBUTORS.md) who participated
in this project.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
for details
## Acknowledgments
- Our colleagues at [MindBridge](https://mindbridge.ai) for discussion and
project planning
- [Parsing Dates with JSON](https://weblog.west-wind.com/posts/2014/Jan/06/JavaScript-JSON-Date-Parsing-and-real-Dates)
for knowledge
- [OAK Server](https://github.com/oakserver/oak) as a project structure example