Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSON types #61

Merged
merged 11 commits into from
Nov 9, 2020
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Changed
The input to fromJson() is now a JsonValue, enforcing basic JSON requirements:
property values must be legal JSON values. This is meant to allow the
compiler to flag accidental deserializations of already-deserialized objects.

### Added

## [v0.3.0] - 2020-11-6
## [v0.3.0] - 2020-11-06

### Changed
- updated node example to be a properly formatted node module
Expand Down
2 changes: 1 addition & 1 deletion from_json/as.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FromJsonStrategy, Serializable } from "../serializable.ts";
export function fromJsonAs<T>(
type: T & { new (): Serializable },
): FromJsonStrategy {
return (value: T) => {
return (value) => {
if (Array.isArray(value)) {
return value.map((item) => new type().fromJson(item));
}
Expand Down
4 changes: 2 additions & 2 deletions from_json/as_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ test({
test = true;
}
const testObj = new Test();
assertEquals(fromJsonAs(Test)(testObj).test, testObj.test);
assert(fromJsonAs(Test)(testObj) instanceof Test);
assertEquals(fromJsonAs(Test)({ test: true }).test, testObj.test);
assert(fromJsonAs(Test)({ test: true }) instanceof Test);
},
});

Expand Down
4 changes: 2 additions & 2 deletions from_json/date.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2018-2020 Gamebridge.ai authors. All rights reserved. MIT license.

import { FromJsonStrategy } from "../serializable.ts";
import { FromJsonStrategy, JsonValue } from "../serializable.ts";

/** allows authors to pass a regex to parse as a date */
export function createDateStrategy(regex: RegExp): FromJsonStrategy {
return (value: any): any | Date => {
return (value: JsonValue): any | Date => {
return typeof value === "string" && regex.exec(value)
? new Date(value)
: value;
Expand Down
47 changes: 30 additions & 17 deletions serializable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ import { SerializePropertyOptionsMap } from "./serialize_property_options_map.ts
import { defaultToJson } from "./to_json/default.ts";
import { recursiveToJson } from "./to_json/recursive.ts";

/** A JSON object where each property value is a simple JSON value. */
export type JsonObject = { [key: string]: JsonValue };

/** A property value in a JSON object. */
export type JsonValue =
| null
| boolean
| number
| string
| JsonValue[]
| JsonObject;

/** to be implemented by external authors on their models */
export declare interface TransformKey {
/** a function that will be called against
Expand All @@ -22,19 +34,18 @@ export abstract class Serializable {
public toJson(): string {
return toJson(this);
}
public fromJson(): this;
public fromJson(json: string): this;
public fromJson(json: Record<string, any>): this;
public fromJson(json: string | Record<string, any> = {}): this {
public fromJson(json: JsonValue): this;
public fromJson(json: string | JsonValue): this {
return fromJson(this, json);
}
}

/** Functions used when hydrating data */
export type FromJsonStrategy = (value: any) => any;
export type FromJsonStrategy = (value: JsonValue) => any;

/** Functions used when dehydrating data */
export type ToJsonStrategy = (value: any) => any;
export type ToJsonStrategy = (value: any) => JsonValue;

/** options to use when (de)serializing values */
export class SerializePropertyOptions {
Expand Down Expand Up @@ -69,13 +80,15 @@ export class SerializePropertyOptions {
* Converts value from functions provided as parameters
*/
export function composeStrategy(
...fns:
| (FromJsonStrategy | FromJsonStrategy[])[]
| (ToJsonStrategy | ToJsonStrategy[])[]
): FromJsonStrategy | ToJsonStrategy {
return (val: unknown): unknown =>
...fns: (FromJsonStrategy | FromJsonStrategy[])[]
): FromJsonStrategy;
export function composeStrategy(
...fns: (ToJsonStrategy | ToJsonStrategy[])[]
): ToJsonStrategy;
export function composeStrategy(...fns: any): any {
return (val: any): any =>
fns.flat().reduce(
(acc: unknown, f: FromJsonStrategy | ToJsonStrategy) => f(acc),
(acc: any, f: FromJsonStrategy | ToJsonStrategy) => f(acc),
val,
);
}
Expand All @@ -95,7 +108,7 @@ const ERROR_MESSAGE_MISSING_PROPERTIES_MAP =
/** Converts to object using mapped keys */
export function toPojo(
context: any,
): Record<string, unknown> {
): JsonObject {
const serializablePropertyMap = SERIALIZABLE_CLASS_MAP.get(
context?.constructor?.prototype,
);
Expand All @@ -106,7 +119,7 @@ export function toPojo(
?.prototype}`,
);
}
const record: Record<string, unknown> = {};
const record: JsonObject = {};
for (
let {
propertyKey,
Expand Down Expand Up @@ -148,16 +161,16 @@ function toJson<T>(context: T): string {
/** Convert from object/string to mapped object on the context */
function fromJson<T>(context: Serializable, json: string): T;

function fromJson<T>(context: Serializable, json: Record<string, any>): T;
function fromJson<T>(context: Serializable, json: JsonValue): T;

function fromJson<T>(
context: Serializable,
json: string | Record<string, any>,
json: string | JsonValue,
): T;

function fromJson<T>(
context: Serializable,
json: string | Record<string, any>,
json: string | JsonValue,
): T {
const serializablePropertyMap = SERIALIZABLE_CLASS_MAP.get(
context?.constructor?.prototype,
Expand All @@ -176,7 +189,7 @@ function fromJson<T>(
JSON.parse(
_json,
/** Processes the value through the provided or default `fromJsonStrategy` */
function revive(key: string, value: unknown): unknown {
function revive(key: string, value: JsonValue): unknown {
// After the last iteration of the fromJsonStrategy a function
// will be called one more time with a empty string key
if (key === "") {
Expand Down
2 changes: 1 addition & 1 deletion serialize_property_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ test({
}
class Test extends Serializable {
@SerializeProperty({
fromJsonStrategy: (v: OtherClass) => new OtherClass().fromJson(v),
fromJsonStrategy: (v) => new OtherClass().fromJson(v),
})
array!: OtherClass[];
}
Expand Down
4 changes: 2 additions & 2 deletions to_json/recursive.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright 2018-2020 Gamebridge.ai authors. All rights reserved. MIT license.

import { Serializable, toPojo } from "../serializable.ts";
import { JsonObject, Serializable, toPojo } from "../serializable.ts";

/** Recursively serialize a serializable class */
export function recursiveToJson(value: Serializable): any {
export function recursiveToJson(value: Serializable): JsonObject {
return toPojo(value);
}