-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(TinyType): Improvements to serialisation/de-serialisation types
- Loading branch information
Showing
11 changed files
with
205 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,7 +78,7 @@ | |
"lib", | ||
"node_modules", | ||
"spec", | ||
"src/types.ts" | ||
"src/types" | ||
], | ||
"extension": [ | ||
".ts" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import 'mocha'; | ||
import { TinyType, TinyTypeOf } from '../src'; | ||
import { JSONObject, Serialised } from '../src/types'; | ||
import { expect } from './expect'; | ||
|
||
describe('Serialisation', () => { | ||
|
||
describe('of TinyTypes wrapping several primitive values', () => { | ||
|
||
class Person extends TinyType { | ||
public role: string = 'dev'; | ||
|
||
static fromJSON(o: Serialised<Person>): Person { | ||
return new Person(o.firstName as string, o.lastName as string, o.age as number); | ||
} | ||
|
||
constructor(public readonly firstName: string, public readonly lastName: string, public readonly age: number) { | ||
super(); | ||
} | ||
|
||
speak() { | ||
return `Hi, I'm ${this.firstName} ${this.lastName}`; | ||
} | ||
} | ||
|
||
it('uses only the significant fields and retains their type', () => { | ||
const p = new Person('John', 'Smith', 42); | ||
const serialised = p.toJSON() as Serialised<Person>; | ||
|
||
expect(Object.keys(serialised)).to.include.ordered.members(['age', 'firstName', 'lastName', 'role']); | ||
expect(Object.keys(serialised)).to.not.include.members(['speak', 'toJSON', 'toString']); | ||
|
||
expect(serialised.age).to.be.a('number'); | ||
expect(serialised.firstName).to.be.a('string'); | ||
expect(serialised.lastName).to.be.a('string'); | ||
expect(serialised.role).to.be.a('string'); | ||
}); | ||
}); | ||
|
||
describe('of nested Tiny Types', () => { | ||
class FirstName extends TinyTypeOf<string>() { | ||
static fromJSON = (v: string) => new FirstName(v); | ||
} | ||
class LastName extends TinyTypeOf<string>() { | ||
static fromJSON = (v: string) => new LastName(v); | ||
} | ||
class Age extends TinyTypeOf<number>() { | ||
static fromJSON = (v: number) => new Age(v); | ||
} | ||
|
||
class AnotherPerson extends TinyType { | ||
public role: string = 'dev'; | ||
|
||
static fromJSON(o: Serialised<AnotherPerson>): AnotherPerson { | ||
return new AnotherPerson( | ||
FirstName.fromJSON(o.firstName as string), | ||
LastName.fromJSON(o.lastName as string), | ||
Age.fromJSON(o.age as number), | ||
); | ||
} | ||
|
||
constructor(public readonly firstName: FirstName, | ||
public readonly lastName: LastName, | ||
public readonly age: Age, | ||
) { | ||
super(); | ||
} | ||
|
||
speak() { | ||
return `Hi, I'm ${this.firstName} ${this.lastName}`; | ||
} | ||
} | ||
|
||
it('uses only the significant fields and retains the type of their respective values', () => { | ||
const p = new AnotherPerson(new FirstName('John'), new LastName('Smith'), new Age(42)); | ||
const serialised = p.toJSON() as Serialised<AnotherPerson>; | ||
|
||
expect(Object.keys(serialised)).to.include.ordered.members(['age', 'firstName', 'lastName', 'role']); | ||
expect(Object.keys(serialised)).to.not.include.members(['speak', 'toJSON', 'toString']); | ||
|
||
expect(serialised.age).to.be.a('number'); | ||
expect(serialised.firstName).to.be.a('string'); | ||
expect(serialised.lastName).to.be.a('string'); | ||
expect(serialised.role).to.be.a('string'); | ||
}); | ||
}); | ||
|
||
it('works both ways', () => { | ||
class EmployeeId extends TinyTypeOf<number>() { | ||
static fromJSON = (id: number) => new EmployeeId(id); | ||
} | ||
|
||
class DepartmentId extends TinyTypeOf<string>() { | ||
static fromJSON = (id: string) => new DepartmentId(id); | ||
} | ||
|
||
class Allocation extends TinyType { | ||
static fromJSON = (o: Serialised<Allocation>) => new Allocation( | ||
EmployeeId.fromJSON(o.employeeId as number), | ||
DepartmentId.fromJSON(o.departmentId as string), | ||
) | ||
|
||
constructor(public readonly employeeId: EmployeeId, public readonly departmentId: DepartmentId) { | ||
super(); | ||
} | ||
} | ||
|
||
const allocation = new Allocation(new EmployeeId(1), new DepartmentId('engineering')); | ||
|
||
const deserialised = Allocation.fromJSON({ departmentId: 'engineering', employeeId: 1 }); | ||
|
||
expect(deserialised.equals(allocation)).to.be.true; // tslint:disable-line:no-unused-expression | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export type ConstructorOrAbstract<T = {}> = Function & { prototype: T }; // tslint:disable-line:ban-types | ||
export type ConstructorAbstractOrInstance<T = {}> = T | ConstructorOrAbstract; // tslint:disable-line:ban-types |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './constructors'; | ||
export * from './json'; | ||
export * from './list'; | ||
export * from './serialisation'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export type Null = null; | ||
export type NonNullJSONPrimitive = string | number | boolean; | ||
export type JSONPrimitive = NonNullJSONPrimitive | 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type List<T> = T[]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Here be dragons 🔥🐉 | ||
|
||
// see https://github.com/Microsoft/TypeScript/issues/18133#issuecomment-325910172 | ||
export type Bool = 'true' | 'false'; | ||
|
||
export type Not<X extends Bool> = { | ||
true: 'false', | ||
false: 'true', | ||
}[X]; | ||
|
||
export type HaveIntersection<S1 extends string, S2 extends string> = ( | ||
{ [K in S1]: 'true' } & | ||
{ [key: string]: 'false' } | ||
)[S2]; | ||
|
||
export type IsNeverWorker<S extends string> = ( | ||
{ [K in S]: 'false' } & | ||
{ [key: string]: 'true' } | ||
)[S]; | ||
|
||
// Worker needed because of https://github.com/Microsoft/TypeScript/issues/18118 | ||
export type IsNever<T extends string> = Not<HaveIntersection<IsNeverWorker<T>, 'false'>>; | ||
|
||
export type IsFunction<T> = IsNever<keyof T>; | ||
|
||
export type NonFunctionProps<T> = { | ||
[K in keyof T]: { | ||
'false': K, | ||
'true': never, | ||
}[IsFunction<T[K]>] | ||
}[keyof T]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { JSONValue } from './json'; | ||
import { NonFunctionProps } from './lookup'; | ||
|
||
export interface Serialisable<S extends JSONValue = JSONValue> { | ||
toJSON(): S; | ||
} | ||
|
||
/** | ||
* @experimental | ||
*/ | ||
export type Serialised<T extends object> = { [P in NonFunctionProps<T>]: JSONValue }; |