Skip to content

Commit

Permalink
feat(TinyType): TinyTypes can be serialised to JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-molak committed Feb 12, 2018
1 parent cc5b8b4 commit 59213dc
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 0 deletions.
46 changes: 46 additions & 0 deletions spec/TinyType.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,50 @@ describe('TinyType', () => {
.to.equal('Postcode(area=Area(value=GU), district=District(value=15), sector=Sector(value=9), unit=Unit(value=NZ))');
});
});

describe('serialisation', () => {

class FirstName extends TinyTypeOf<string>() {}
class LastName extends TinyTypeOf<string>() {}
class Age extends TinyTypeOf<number>() {}
class Person extends TinyType {
constructor(
public readonly firstName: FirstName,
public readonly lastName: LastName,
public readonly age: Age,
) {
super();
}
}

describe('::toJSON', () => {

given<TinyType & { value: any }>(
new FirstName('Bruce'),
new Age(55),
).
it('should serialise a single-value TinyType to just its value', input => {
expect(input.toJSON()).to.equal(input.value);
});

it('should serialise a complex TinyType recursively', () => {

const person = new Person(new FirstName('Bruce'), new LastName('Smith'), new Age(55));

expect(person.toJSON()).to.deep.equal({
firstName: 'Bruce',
lastName: 'Smith',
age: 55,
});
});

it(`should JSON.stringify any object that can't be represented in a more sensible way`, () => {
class TT extends TinyTypeOf<object>() {}

const tt = new TT(new Object({ key: 'value' }));

expect(tt.toJSON()).to.equal('{"key":"value"}');
});
});
});
});
50 changes: 50 additions & 0 deletions spec/json.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'mocha';
import { JSONArray, JSONObject, JSONPrimitive, JSONValue } from '../src/types';

describe('JSON', () => {

const
Some_String: string = 'string',
Some_Number: number = 1,
Some_Boolean: boolean = false,
Some_Object = {k1: Some_String, k2: Some_Number},
Some_Array = [Some_String, Some_Number, Some_Boolean, Some_Object];

describe('JSONArray', () => {
it(`describes an array that's a valid JSON`, () => {
const array: JSONArray = Some_Array;
});
});

describe('JSONObject', () => {
it(`describes a JavaScript object serialised to JSON`, () => {
const object: JSONObject = {
string: Some_String,
number: Some_Number,
boolean: Some_Boolean,
object: Some_Object,
array: Some_Array,
};
});
});

describe('JSONPrimitive', () => {
it(`describes any primitive that can be part of JSON`, () => {
const s: JSONPrimitive = 'string',
n: JSONPrimitive = 42,
b: JSONPrimitive = false,
e: JSONPrimitive = null;
});
});

describe('JSONValue', () => {
it('describes any value that can be represented as JSON', () => {
const
ss: JSONValue = Some_String,
sn: JSONValue = Some_Number,
sb: JSONValue = Some_Boolean,
so: JSONValue = Some_Object,
sa: JSONValue = Some_Array;
});
});
});
27 changes: 27 additions & 0 deletions src/TinyType.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { JSONValue } from './types';

export function TinyTypeOf<T>(): { new(_: T): { value: T } & TinyType } {
return class extends TinyType {
constructor(public readonly value: T) {
Expand Down Expand Up @@ -35,6 +37,31 @@ export abstract class TinyType {
return `${this.constructor.name}(${fields.join(', ')})`;
}

toJSON(): JSONValue {
const isPrimitive = (value: any) => Object(value) !== value;
function toJSON(value: any) {
switch (true) {
case value && !! value.toJSON:
return value.toJSON();
case value && ! isPrimitive(value):
return JSON.stringify(value);
default:
return value;
}
}

const fields = this.fields();

if (fields.length === 1) {
return toJSON(this[fields[0]]);
}

return fields.reduce((acc, field) => {
acc[field] = toJSON(this[field]);
return acc;
}, {});
}

private fields() {
return Object.getOwnPropertyNames(this)
.filter(field => typeof this[field] !== 'function')
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
export type List<T> = T[];
export type ConstructorOrAbstract<T = {}> = Function & { prototype: T }; // tslint:disable-line:ban-types

export type JSONPrimitive = string | number | boolean | null;
export interface JSONObject {
[_: string]: JSONPrimitive | JSONObject | JSONArray;
}
export interface JSONArray extends Array<JSONValue> {} // tslint:disable-line:no-empty-interface
export type JSONValue = JSONPrimitive | JSONObject | JSONArray;

0 comments on commit 59213dc

Please sign in to comment.