UNPKG

@deepkit/bson

Version:
955 lines (752 loc) 39 kB
import { ClassType } from '@deepkit/core'; import { ClassSchema, cloneClass, FieldDecoratorResult, getClassSchema, isFieldDecorator, plainToClass, propertyDefinition, t, unpopulatedSymbol } from '@deepkit/type'; import 'reflect-metadata'; import { getBSONDecoder } from '../src/bson-jit-parser'; import { getBSONSerializer, serialize } from '../src/bson-serialize'; import { expect, test } from '@jest/globals'; import { deserialize } from '../src/bson-parser'; import crypto from 'crypto'; import { ObjectId, UUID } from '../src/model'; (BigInt.prototype as any).toJSON = function () { return this.toString(); }; Object.defineProperty(global, 'crypto', { value: { getRandomValues: arr => crypto.randomBytes(arr.length) } }); /** * When the value is not existent anymore (don't confuse with being undefined). * Equal to check with `in`. */ export const RoundTripExcluded: unique symbol = Symbol('RoundTripExcluded'); export function roundTrip<T>(s: ClassSchema<T> | ClassType<T> | FieldDecoratorResult<T>, value: T): T { if (isFieldDecorator(s)) { const wrapped = t.schema({ field: s }); const serializer = getBSONSerializer(wrapped); const deserializer = getBSONDecoder(wrapped); const bson = serializer({ field: value }); // console.log('serialized', deserialize(bson)); const item = deserializer(bson); if ('field' in item) return (item as any).field; return RoundTripExcluded as any; } else { const serializer = getBSONSerializer(s); const deserializer = getBSONDecoder(s); const bson = serializer(value); // console.log('serialized', deserialize(bson)); const item = deserializer(bson); return item as any; } } export function serializeToJson<T>(s: ClassSchema<T> | ClassType<T> | FieldDecoratorResult<T>, value: T): T { if (isFieldDecorator(s)) { const wrapped = t.schema({ v: s }); const serializer = getBSONSerializer(wrapped); const bson = serializer({ v: value }); return deserialize(bson).v; } else { const serializer = getBSONSerializer(s); const bson = serializer(value); return deserialize(bson) as any; } } export function deserializeFromJson<T>(s: ClassSchema<T> | ClassType<T> | FieldDecoratorResult<T>, value: T): T { if (isFieldDecorator(s)) { const wrapped = t.schema({ v: s }); const deserializer = getBSONDecoder(wrapped); const bson = serialize({ v: value }); return (deserializer(bson) as any).v; } else { const deserializer = getBSONDecoder(s); const bson = serialize(value); return deserializer(bson); } } test('wrapper types', () => { expect(roundTrip(t.uuid, new UUID('5c58e474-82ad-4e92-ad9f-487bbc11e8ed') as any)).toEqual('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(roundTrip(t.mongoId, new ObjectId('507f1f77bcf86cd799439011') as any)).toEqual('507f1f77bcf86cd799439011'); expect(roundTrip(t.any, new UUID('5c58e474-82ad-4e92-ad9f-487bbc11e8ed') as any)).toEqual('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(roundTrip(t.any, new ObjectId('507f1f77bcf86cd799439011') as any)).toEqual('507f1f77bcf86cd799439011'); }); test('objectid generate', () => { expect(ObjectId.generate().length).toBe(24); expect(ObjectId.generate()).not.toBe(ObjectId.generate()); }); test('uuid and objectId not detected as class type', () => { class Model { constructor(@t public name: string) { } } expect(serializeToJson(t.type(Model), new Model('foo'))).toEqual({ name: 'foo' }); expect(serializeToJson(t.type(Model), new UUID('adasd') as any)).toEqual(undefined); expect(serializeToJson(t.type(Model), new ObjectId('adasd') as any)).toEqual(undefined); }); // // //! Beginning from here, the content is copied from @deepkit/type/tests/type-spec.spec.ts // // enum MyEnum { a, b, c } class Config { @t color: string = '#fff'; @t big: boolean = false; } class Model { @t id: number = 0; @t title: string = ''; @t config?: Config; } test('basics with value', () => { expect(roundTrip(t.string, 'asd')).toBe('asd'); expect(roundTrip(t.number, 22)).toBe(22); expect(roundTrip(t.boolean, false)).toBe(false); expect(roundTrip(t.date, new Date)).toBeInstanceOf(Date); }); test('model', () => { { const item = new Model; item.id = 23; item.title = '2322'; const back = roundTrip(Model, item); expect(back).toEqual({ id: 23, title: '2322' }); expect(back).toBeInstanceOf(Model); } }); test('string', () => { expect(roundTrip(t.string, "0123456789")).toEqual("0123456789"); expect(roundTrip(t.string, "012345678901234567890123456789")).toEqual("012345678901234567890123456789"); expect(roundTrip(t.string, "0123456789012345678901234567890123456789")).toEqual("0123456789012345678901234567890123456789"); expect(roundTrip(t.string, "0123456789".repeat(64))).toEqual("0123456789".repeat(64)); }); test('with implicit default value', () => { const defaultDate = new Date; class Product { @t id: number = 0; @t created: Date = defaultDate; } //having a default value doesn't mean we are optional; expect(getClassSchema(Product).getProperty('created').isOptional).toBe(false); expect(roundTrip(Product, { id: 23 } as any)).toEqual({ id: 23, created: defaultDate }); expect(roundTrip(Product, { id: 23, created: undefined } as any)).toEqual({ id: 23, created: defaultDate }); expect(roundTrip(t.partial(Product), { id: 23 } as any)).toEqual({ id: 23 }); expect(roundTrip(t.partial(Product), { id: 23, created: undefined } as any)).toEqual({ id: 23 }); //not set properties are ommited expect('created' in roundTrip(t.partial(Product), { id: 23 } as any)).toEqual(false); //we need to keep undefined values otherwise there is not way to reset a value //for JSON/BSON on the transport layer is null used to communicate the fact that we set explictely `created` to undefined expect('created' in roundTrip(t.partial(Product), { id: 23, created: undefined } as any)).toEqual(true); }); test('t.schema', () => { { const s = t.schema({ type: t.literal('m'), name: t.string }); const item = roundTrip(s, { type: 'm' as 'm', name: 'peter' }); expect(item).toEqual({ type: 'm', name: 'peter' }); expect(item).toBeInstanceOf(s.classType); } }); test('partial keeps explicitely undefined fields', () => { expect(roundTrip(t.partial(Model), {})).toEqual({}); expect('name' in roundTrip(t.partial(Model), {})).toBe(false); expect(roundTrip(t.partial(Model), { title: undefined })).toEqual({ title: undefined }); { const item = roundTrip(t.partial(Model), { title: undefined }); expect('title' in item).toBe(true); //all fields in partial become optional } { const item = roundTrip(t.partial(Model), {}); expect('title' in item).toBe(false); } class Purchase { @t.primary.autoIncrement id: number = 0; @t sentAt?: Date; @t canceledAt?: Date; } expect(roundTrip(t.partial(Purchase), { sentAt: undefined })).toEqual({ sentAt: undefined }); expect('sentAt' in roundTrip(t.partial(Purchase), { sentAt: undefined })).toEqual(true); }); test('any undefined', () => { expect(serializeToJson(t.any, undefined)).toEqual(null); }); test('any invalid structure', () => { class SQLiteDatabase {} expect(serializeToJson(t.any, SQLiteDatabase)).toEqual(undefined); }); test('any keys', () => { const o = {$___newId: 5}; expect(serializeToJson(t.any, o)).toEqual(o); }); test('map removes undefined when not allowed', () => { expect(roundTrip(t.map(t.string), {})).toEqual({}); expect(roundTrip(t.map(t.string), { foo: 'bar' })).toEqual({ foo: 'bar' }); expect(roundTrip(t.map(t.string), { foo: undefined } as any)).toEqual({}); expect('foo' in roundTrip(t.map(t.string), { foo: undefined } as any)).toEqual(false); }); test('map allows undefined when allowed', () => { expect(roundTrip(t.map(t.string.optional), {})).toEqual({}); expect(roundTrip(t.map(t.string.optional), { foo: 'bar' })).toEqual({ foo: 'bar' }); expect(roundTrip(t.map(t.string.optional), { foo: undefined } as any)).toEqual({ foo: undefined }); expect('foo' in roundTrip(t.map(t.string.optional), { foo: undefined } as any)).toEqual(true); }); test('bigint', () => { expect(roundTrip(t.bigint, -12n)).toEqual(-12n); expect(roundTrip(t.bigint, -5n)).toEqual(-5n); expect(roundTrip(t.bigint, -1n)).toEqual(-1n); expect(roundTrip(t.bigint, 0n)).toEqual(0n); expect(roundTrip(t.bigint, 5n)).toEqual(5n); expect(roundTrip(t.bigint, 12n)).toEqual(12n); expect(roundTrip(t.bigint, 12012020202020202020202020202020202020n)).toEqual(12012020202020202020202020202020202020n); expect(roundTrip(t.bigint, 16n**16n**2n)).toEqual(16n**16n**2n); expect(roundTrip(t.bigint, 16n**16n**3n)).toEqual(16n**16n**3n); }); test('propertyDefinition', () => { const property = t.string.optional.default('asd').buildPropertySchema(); const json = property.toJSON(); expect(roundTrip(propertyDefinition, json)).toEqual(json); }); test('union basics', () => { expect(roundTrip(t.union(t.string, t.number), 'asd')).toEqual('asd'); expect(roundTrip(t.union(t.string, t.number), 23)).toEqual(23); expect(roundTrip(t.union(t.boolean, t.number), true)).toEqual(true); expect(roundTrip(t.union(t.boolean, t.number), 23)).toEqual(23); expect(roundTrip(t.union(t.bigint, t.number), 23)).toEqual(23); expect(roundTrip(t.union(t.bigint, t.number), 23n)).toEqual(23n); expect(roundTrip(t.union(t.bigint, t.number), 9223372036854775810n)).toEqual(9223372036854775810n); expect(roundTrip(t.union(t.string, Model), new Model)).toBeInstanceOf(Model); { const item = new Model; item.id = 23; item.title = '23'; const back = roundTrip(t.union(t.string, Model), item); expect(back).toEqual({ id: 23, title: '23' }); } { const item = new Model; item.id = 23; item.title = '23'; const back = roundTrip(t.union(Model), item); expect(back).toEqual({ id: 23, title: '23' }); } expect(roundTrip(t.union(t.string, Model), 'asd')).toEqual('asd'); expect(roundTrip(t.union(t.string, Model).optional, undefined)).toEqual(undefined); expect(roundTrip(t.union(t.string, Model).optional, null)).toEqual(undefined); expect(roundTrip(t.union(t.string, Model).nullable, undefined)).toEqual(null); expect(roundTrip(t.union(t.string, Model).nullable, null)).toEqual(null); }); test('union 2', () => { const s = t.schema({ type: t.literal('m'), name: t.string }); expect(deserializeFromJson(t.union(s), { type: 'm', name: 'Peter' })).toEqual({ type: 'm', name: 'Peter' }); expect(deserializeFromJson(t.union(s), { name: 'Peter' } as any)).toEqual({ type: 'm', name: 'Peter' }); expect(serializeToJson(t.union(s), { type: 'm', name: 'Peter' })).toEqual({ type: 'm', name: 'Peter' }); expect(serializeToJson(t.union(s), { name: 'Peter' } as any)).toEqual({ type: 'm', name: 'Peter' }); expect(roundTrip(t.union(s), { type: 'm', name: 'Peter' })).toEqual({ type: 'm', name: 'Peter' }); expect(roundTrip(t.union(s), { name: 'Peter' } as any)).toEqual({ type: 'm', name: 'Peter' }); }); test('union 3', () => { expect(roundTrip(t.union(t.string, Model), 'asd')).toBe('asd'); expect(roundTrip(t.union(t.string, Model), { title: 'foo' } as any)).toBeInstanceOf(Model); expect(deserializeFromJson(t.union(t.string, Model), 'asd')).toBe('asd'); expect(deserializeFromJson(t.union(t.string, Model), { title: 'foo' } as any)).toEqual({ id: 0, title: 'foo' }); expect(deserializeFromJson(t.union(t.string, Model).optional, undefined)).toBe(undefined); expect(deserializeFromJson(t.union(t.string, Model).nullable, null)).toBe(null); expect(serializeToJson(t.union(t.string, Model), 'asd')).toBe('asd'); expect(serializeToJson(t.union(t.string, Model), { title: 'foo' } as any)).toEqual({ title: 'foo' }); expect(serializeToJson(t.union(t.string, Model).optional, undefined)).toBe(null); expect(serializeToJson(t.union(t.string, Model).nullable, null)).toBe(null); }); test('model 1', () => { class Model { //filter is not used yet @t.map(t.any).optional filter?: any; @t.number.optional skip?: number; @t.number itemsPerPage: number = 50; @t.number.optional limit?: number; @t.map(t.any) parameters: { [name: string]: any } = {}; @t.map(t.any).optional sort?: any; } { const model = { filter: { $regex: /Peter/ }, itemsPerPage: 50, parameters: {} }; expect(roundTrip(Model, model as any)).toEqual(model); } { const model = { itemsPerPage: 50, parameters: { teamName: 'Team a' }, filter: undefined, skip: undefined, limit: undefined, sort: undefined }; expect(roundTrip(Model, model as any)).toEqual(model); } }); class Team { @t.primary.autoIncrement id: number = 0; @t version: number = 0; @t.type(() => User).reference() lead: User | undefined; constructor(@t public name: string) { } } class User { @t.primary.autoIncrement id: number = 0; @t version: number = 0; @t.array(Team).backReference({ via: () => UserTeam }) teams: Team[] = []; constructor(@t public name: string) { } } class UserTeam { @t.primary.autoIncrement id: number = 0; @t version: number = 0; constructor( @t.reference() public team: Team, @t.reference() public user: User, ) { } } test('relation 1', () => { { const user = new User('foo'); expect(roundTrip(User, user)).toEqual(user); } { const team = new Team('foo'); expect(roundTrip(Team, team)).toEqual(team); } { const team = new Team('foo'); const user = new User('foo'); user.id = 12; team.lead = user; expect(serializeToJson(Team, team)).toEqual(team); expect(deserializeFromJson(Team, team)).toEqual(team); expect(roundTrip(Team, team)).toEqual(team); } { const team = new Team('foo'); (team as any).lead = 12; //an ORM might set the primary key directly expect(serializeToJson(Team, team)).toEqual(team); expect(deserializeFromJson(Team, team)).toEqual(team); expect(roundTrip(Team, team)).toEqual(team); } }); test('relation 2', () => { { const user = new User('foo'); user.teams = unpopulatedSymbol as any; //emulates an unpopulated relation const user2 = cloneClass(user); user2.teams = []; expect(roundTrip(User, user)).toEqual(user2); } { const user = new User('foo'); user.teams.push(new Team('bar')); expect(serializeToJson(User, user)).toEqual(user); expect(roundTrip(User, user)).toEqual(user); } { const items: User[] = [ plainToClass(User, { name: 'Peter 1', id: '3f217d3d-4dc4-478f-a969-31e02de37190', version: 0, }), plainToClass(User, { name: 'Peter 2', id: '48f7e8a2-3342-4cee-8de0-c41dcb557454', version: 0, }), plainToClass(User, { name: 'Marc 1', id: 'f673a85b-c31d-41b5-8ccd-67f2bbc2d02e', version: 0, }) ]; expect(roundTrip(t.array(User), items)).toEqual(items); } }); test('invalid', () => { expect(roundTrip(t.uuid, new Model as any)).toEqual(RoundTripExcluded); }); test('regex', () => { expect(roundTrip(t.any, /foo/)).toEqual(/foo/); }); test('explicitely set undefined on optional triggers default value', () => { class Product { @t id: number = 0; @t.optional created?: Date = new Date; } //no value means the default triggers expect(roundTrip(Product, { id: 23 }).created).toBeInstanceOf(Date); //this is important for database patches expect(roundTrip(Product, { id: 23, created: undefined }).created).toBe(undefined); expect('created' in roundTrip(Product, { id: 23, created: undefined })).toBe(true); }); test('partial explicitely set undefined on optional is handled', () => { class Product { @t id: number = 0; @t.optional created?: Date = new Date; } //no value means the default triggers expect(roundTrip(t.partial(Product), { id: 23 }).created).toBe(undefined); //this is important for database patches expect(roundTrip(t.partial(Product), { id: 23, created: undefined }).created).toBe(undefined); expect('created' in roundTrip(t.partial(Product), { id: 23, created: undefined })).toBe(true); }); test('partial explicitely set undefined on required is not ignored', () => { class Product { @t id: number = 0; @t created: Date = new Date; } //no value means the default triggers expect(roundTrip(t.partial(Product), { id: 23 }).created).toBe(undefined); //this is important for database patches //important to keep undefined, as t.partial() makes all properties optional, no matter what it originally was, otherwise it would be a partial expect(roundTrip(t.partial(Product), { id: 23, created: undefined }).created).toBe(undefined); expect('created' in roundTrip(t.partial(Product), { id: 23, created: undefined } as any)).toBe(true); }); test('explicitely set undefined on required is ignored', () => { class Product { @t id: number = 0; @t created: Date = new Date; } expect(roundTrip(Product, { id: 23 } as any).created).toBeInstanceOf(Date); expect(roundTrip(Product, { id: 23, created: undefined } as any).created).toBeInstanceOf(Date); }); test('partial does not return the model on root', () => { expect(roundTrip(t.partial(Model), { id: 23 } as any)).toEqual({ id: 23 }); expect(roundTrip(t.partial(Model), { id: 23 } as any)).not.toBeInstanceOf(Model); }); test('partial returns the model at second level', () => { const config = new Config; config.color = 'red'; expect(roundTrip(t.partial(Model), { id: 23, config: config } as any)).toEqual({ id: 23, config: { big: false, color: 'red' } }); expect(roundTrip(t.partial(Model), { id: 23, config: config } as any).config).toBeInstanceOf(Config); }); test('partial allowed undefined', () => { class Product { @t id: number = 0; @t created?: Date; } expect(roundTrip(t.partial(Product), { id: 23, created: undefined } as any)).not.toBeInstanceOf(Product); expect(roundTrip(t.partial(Product), { id: 23 } as any).created).toBe(undefined); expect('created' in roundTrip(t.partial(Product), { id: 23 } as any)).toBe(false); //important for databas epatches expect(roundTrip(t.partial(Product), { id: 23, created: undefined } as any).created).toBe(undefined); expect('created' in roundTrip(t.partial(Product), { id: 23, created: undefined } as any)).toBe(true); }) test('optional basics', () => { expect(roundTrip(t.string.optional, undefined)).toBe(undefined); expect(roundTrip(t.string.optional, null)).toBe(undefined); expect(roundTrip(t.number.optional, undefined)).toBe(undefined); expect(roundTrip(t.number.optional, null)).toBe(undefined); expect(roundTrip(t.boolean.optional, undefined)).toBe(undefined); expect(roundTrip(t.boolean.optional, null)).toBe(undefined); expect(roundTrip(t.uuid.optional, undefined)).toBe(undefined); expect(roundTrip(t.uuid.optional, null)).toBe(undefined); expect(roundTrip(t.mongoId.optional, undefined)).toBe(undefined); expect(roundTrip(t.mongoId.optional, null)).toBe(undefined); expect(roundTrip(t.date.optional, undefined)).toBe(undefined); expect(roundTrip(t.date.optional, null)).toBe(undefined); expect(roundTrip(t.map(t.any).optional, undefined)).toBe(undefined); expect(roundTrip(t.map(t.any).optional, null)).toBe(undefined); expect(roundTrip(t.array(t.any).optional, undefined)).toBe(undefined); expect(roundTrip(t.array(t.any).optional, null)).toBe(undefined); expect(roundTrip(t.partial({ a: t.string }).optional, undefined)).toBe(undefined); expect(roundTrip(t.partial({ a: t.string }).optional, null)).toBe(undefined); // expect(roundTrip(t.patch({a: t.string}).optional, undefined)).toBe(undefined); // expect(roundTrip(t.patch({a: t.string}).optional, null)).toBe(undefined); expect(roundTrip(t.union(t.string).optional, undefined)).toBe(undefined); expect(roundTrip(t.union(t.string).optional, null)).toBe(undefined); expect(roundTrip(t.literal('a').optional, undefined)).toBe(undefined); expect(roundTrip(t.literal('a').optional, null)).toBe(undefined); expect(roundTrip(t.enum(MyEnum).optional, undefined)).toBe(undefined); expect(roundTrip(t.enum(MyEnum).optional, null)).toBe(undefined); expect(roundTrip(t.type(Model).optional, undefined)).toBe(undefined); expect(roundTrip(t.type(Model).optional, null)).toBe(undefined); expect(roundTrip(t.type(ArrayBuffer).optional, undefined)).toBe(undefined); expect(roundTrip(t.type(ArrayBuffer).optional, null)).toBe(undefined); expect(roundTrip(t.type(Uint8Array).optional, undefined)).toBe(undefined); expect(roundTrip(t.type(Uint8Array).optional, null)).toBe(undefined); }); test('nullable container', () => { const s = t.schema({ tags: t.array(t.string).nullable, tagMap: t.map(t.string).nullable, tagPartial: t.partial({ name: t.string }).nullable, }); expect(roundTrip(s, { tags: null, tagMap: null, tagPartial: null })).toEqual({ tags: null, tagMap: null, tagPartial: null }); expect(roundTrip(s, {} as any)).toEqual({ tags: null, tagMap: null, tagPartial: null }); expect(serializeToJson(s, {} as any)).toEqual({ tags: null, tagMap: null, tagPartial: null }); }); test('map serializes default', () => { const defaultMap = {}; expect(serializeToJson(t.map(t.any).optional.default(defaultMap), undefined)).not.toBe(defaultMap); expect(serializeToJson(t.map(t.any).optional.default(defaultMap), undefined)).toBeInstanceOf(Object); expect(serializeToJson(t.map(t.any).optional.default(defaultMap), null)).toBeInstanceOf(Object); expect(serializeToJson(t.map(t.any).optional.default(defaultMap), null)).not.toBe(defaultMap); }); test('serialize default', () => { expect(serializeToJson(t.string.optional.default('123'), undefined)).toBe('123'); expect(serializeToJson(t.string.optional.default('123'), null)).toBe('123'); expect(serializeToJson(t.number.optional.default(123), undefined)).toBe(123); expect(serializeToJson(t.number.optional.default(123), null)).toBe(123); expect(serializeToJson(t.boolean.optional.default(false), undefined)).toBe(false); expect(serializeToJson(t.boolean.optional.default(false), null)).toBe(false); expect(serializeToJson(t.uuid.optional.default('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'), undefined)).toBe('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(serializeToJson(t.uuid.optional.default('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'), null)).toBe('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(serializeToJson(t.mongoId.optional.default('507f1f77bcf86cd799439011'), undefined)).toBe('507f1f77bcf86cd799439011'); expect(serializeToJson(t.mongoId.optional.default('507f1f77bcf86cd799439011'), null)).toBe('507f1f77bcf86cd799439011'); expect(serializeToJson(t.date.optional.default(new Date), undefined)).toBeInstanceOf(Date); expect(serializeToJson(t.date.optional.default(new Date), null)).toBeInstanceOf(Date); const defaultMap = {}; expect(serializeToJson(t.map(t.any).optional.default(defaultMap), undefined)).not.toBe(defaultMap); expect(serializeToJson(t.map(t.any).optional.default(defaultMap), undefined)).toBeInstanceOf(Object); expect(serializeToJson(t.map(t.any).optional.default(defaultMap), null)).toBeInstanceOf(Object); expect(serializeToJson(t.map(t.any).optional.default(defaultMap), null)).not.toBe(defaultMap); const defaultArray: any[] = []; expect(serializeToJson(t.array(t.any).optional.default(defaultArray), undefined)).not.toBe(defaultArray); expect(serializeToJson(t.array(t.any).optional.default(defaultArray), undefined)).toBeInstanceOf(Array); expect(serializeToJson(t.array(t.any).optional.default(defaultArray), null)).toBeInstanceOf(Array); expect(serializeToJson(t.array(t.any).optional.default(defaultArray), null)).not.toBe(defaultArray); expect(serializeToJson(t.partial({ a: t.string }).optional.default(defaultMap), undefined)).not.toBe(defaultMap); expect(serializeToJson(t.partial({ a: t.string }).optional.default(defaultMap), undefined)).toBeInstanceOf(Object); expect(serializeToJson(t.partial({ a: t.string }).optional.default(defaultMap), null)).not.toBe(defaultMap); expect(serializeToJson(t.partial({ a: t.string }).optional.default(defaultMap), null)).toBeInstanceOf(Object); expect(serializeToJson(t.union(t.string).optional.default('asd'), undefined)).toBe('asd'); expect(serializeToJson(t.union(t.string).optional.default('asd'), null)).toBe('asd'); expect(serializeToJson(t.literal('a').optional.default('a'), undefined)).toBe('a'); expect(serializeToJson(t.literal('a').optional.default('a'), null)).toBe('a'); expect(serializeToJson(t.enum(MyEnum).optional.default(MyEnum.b), undefined)).toBe(MyEnum.b); expect(serializeToJson(t.enum(MyEnum).optional.default(MyEnum.b), null)).toBe(MyEnum.b); expect(serializeToJson(t.type(Model).optional.default(() => new Model), undefined)).toBeInstanceOf(Object); expect(serializeToJson(t.type(Model).optional.default(() => new Model), null)).toBeInstanceOf(Object); }); test('deserialize default', () => { expect(deserializeFromJson(t.string.optional.default('123'), undefined)).toBe('123'); expect(deserializeFromJson(t.string.optional.default('123'), null)).toBe('123'); expect(deserializeFromJson(t.number.optional.default(123), undefined)).toBe(123); expect(deserializeFromJson(t.number.optional.default(123), null)).toBe(123); expect(deserializeFromJson(t.boolean.optional.default(false), undefined)).toBe(false); expect(deserializeFromJson(t.boolean.optional.default(false), null)).toBe(false); expect(deserializeFromJson(t.uuid.optional.default('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'), undefined)).toBe('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(deserializeFromJson(t.uuid.optional.default('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'), null)).toBe('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(deserializeFromJson(t.mongoId.optional.default('507f1f77bcf86cd799439011'), undefined)).toBe('507f1f77bcf86cd799439011'); expect(deserializeFromJson(t.mongoId.optional.default('507f1f77bcf86cd799439011'), null)).toBe('507f1f77bcf86cd799439011'); expect(deserializeFromJson(t.date.optional.default(new Date), undefined)).toBeInstanceOf(Date); expect(deserializeFromJson(t.date.optional.default(new Date), null)).toBeInstanceOf(Date); const defaultMap = {}; expect(deserializeFromJson(t.map(t.any).optional.default(defaultMap), undefined)).not.toBe(defaultMap); expect(deserializeFromJson(t.map(t.any).optional.default(defaultMap), undefined)).toBeInstanceOf(Object); expect(deserializeFromJson(t.map(t.any).optional.default(defaultMap), null)).toBeInstanceOf(Object); expect(deserializeFromJson(t.map(t.any).optional.default(defaultMap), null)).not.toBe(defaultMap); const defaultArray: any[] = []; expect(deserializeFromJson(t.array(t.any).optional.default(defaultArray), undefined)).not.toBe(defaultArray); expect(deserializeFromJson(t.array(t.any).optional.default(defaultArray), undefined)).toBeInstanceOf(Array); expect(deserializeFromJson(t.array(t.any).optional.default(defaultArray), null)).toBeInstanceOf(Array); expect(deserializeFromJson(t.array(t.any).optional.default(defaultArray), null)).not.toBe(defaultArray); expect(deserializeFromJson(t.partial({ a: t.string }).optional.default(defaultMap), undefined)).not.toBe(defaultMap); expect(deserializeFromJson(t.partial({ a: t.string }).optional.default(defaultMap), undefined)).toBeInstanceOf(Object); expect(deserializeFromJson(t.partial({ a: t.string }).optional.default(defaultMap), null)).not.toBe(defaultMap); expect(deserializeFromJson(t.partial({ a: t.string }).optional.default(defaultMap), null)).toBeInstanceOf(Object); expect(deserializeFromJson(t.union(t.string).optional.default('asd'), undefined)).toBe('asd'); expect(deserializeFromJson(t.union(t.string).optional.default('asd'), null)).toBe('asd'); expect(deserializeFromJson(t.literal('a').optional.default('a'), undefined)).toBe('a'); expect(deserializeFromJson(t.literal('a').optional.default('a'), null)).toBe('a'); expect(deserializeFromJson(t.enum(MyEnum).optional.default(MyEnum.b), undefined)).toBe(MyEnum.b); expect(deserializeFromJson(t.enum(MyEnum).optional.default(MyEnum.b), null)).toBe(MyEnum.b); const defaultModel = new Model; expect(deserializeFromJson(t.type(Model).optional.default(() => new Model), undefined)).not.toBe(defaultModel); expect(deserializeFromJson(t.type(Model).optional.default(() => new Model), undefined)).toBeInstanceOf(Model); expect(deserializeFromJson(t.type(Model).optional.default(() => new Model), null)).not.toBe(defaultModel); expect(deserializeFromJson(t.type(Model).optional.default(() => new Model), null)).toBeInstanceOf(Model); }); test('default basics', () => { expect(roundTrip(t.string.optional.default('123'), undefined)).toBe('123'); expect(roundTrip(t.string.optional.default('123'), null)).toBe('123'); expect(roundTrip(t.number.optional.default(123), undefined)).toBe(123); expect(roundTrip(t.number.optional.default(123), null)).toBe(123); expect(roundTrip(t.boolean.optional.default(false), undefined)).toBe(false); expect(roundTrip(t.boolean.optional.default(false), null)).toBe(false); expect(roundTrip(t.uuid.optional.default('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'), undefined)).toBe('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(roundTrip(t.uuid.optional.default('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'), null)).toBe('5c58e474-82ad-4e92-ad9f-487bbc11e8ed'); expect(roundTrip(t.mongoId.optional.default('507f1f77bcf86cd799439011'), undefined)).toBe('507f1f77bcf86cd799439011'); expect(roundTrip(t.mongoId.optional.default('507f1f77bcf86cd799439011'), null)).toBe('507f1f77bcf86cd799439011'); expect(roundTrip(t.date.optional.default(new Date), undefined)).toBeInstanceOf(Date); expect(roundTrip(t.date.optional.default(new Date), null)).toBeInstanceOf(Date); const defaultMap = {}; expect(roundTrip(t.map(t.any).optional.default(defaultMap), undefined)).not.toBe(defaultMap); expect(roundTrip(t.map(t.any).optional.default(defaultMap), undefined)).toBeInstanceOf(Object); expect(roundTrip(t.map(t.any).optional.default(defaultMap), null)).toBeInstanceOf(Object); expect(roundTrip(t.map(t.any).optional.default(defaultMap), null)).not.toBe(defaultMap); const defaultArray: any[] = []; expect(roundTrip(t.array(t.any).optional.default(defaultArray), undefined)).not.toBe(defaultArray); expect(roundTrip(t.array(t.any).optional.default(defaultArray), undefined)).toBeInstanceOf(Array); expect(roundTrip(t.array(t.any).optional.default(defaultArray), null)).toBeInstanceOf(Array); expect(roundTrip(t.array(t.any).optional.default(defaultArray), null)).not.toBe(defaultArray); expect(roundTrip(t.partial({ a: t.string }).optional.default(defaultMap), undefined)).not.toBe(defaultMap); expect(roundTrip(t.partial({ a: t.string }).optional.default(defaultMap), undefined)).toBeInstanceOf(Object); expect(roundTrip(t.partial({ a: t.string }).optional.default(defaultMap), null)).not.toBe(defaultMap); expect(roundTrip(t.partial({ a: t.string }).optional.default(defaultMap), null)).toBeInstanceOf(Object); expect(roundTrip(t.union(t.string).optional.default('asd'), undefined)).toBe('asd'); expect(roundTrip(t.union(t.string).optional.default('asd'), null)).toBe('asd'); expect(roundTrip(t.literal('a').optional.default('a'), undefined)).toBe('a'); expect(roundTrip(t.literal('a').optional.default('a'), null)).toBe('a'); expect(roundTrip(t.enum(MyEnum).optional.default(MyEnum.b), undefined)).toBe(MyEnum.b); expect(roundTrip(t.enum(MyEnum).optional.default(MyEnum.b), null)).toBe(MyEnum.b); const defaultModel = new Model; expect(roundTrip(t.type(Model).optional.default(defaultModel), undefined)).not.toBe(defaultModel); expect(roundTrip(t.type(Model).optional.default(defaultModel), undefined)).toBeInstanceOf(Model); expect(roundTrip(t.type(Model).optional.default(defaultModel), null)).not.toBe(defaultModel); expect(roundTrip(t.type(Model).optional.default(defaultModel), null)).toBeInstanceOf(Model); }); test('nullable basics', () => { expect(roundTrip(t.string.nullable, undefined)).toBe(null); expect(roundTrip(t.string.nullable, null)).toBe(null); expect(roundTrip(t.number.nullable, undefined)).toBe(null); expect(roundTrip(t.number.nullable, null)).toBe(null); expect(roundTrip(t.boolean.nullable, undefined)).toBe(null); expect(roundTrip(t.boolean.nullable, null)).toBe(null); expect(roundTrip(t.date.nullable, undefined)).toBe(null); expect(roundTrip(t.date.nullable, null)).toBe(null); expect(roundTrip(t.uuid.nullable, undefined)).toBe(null); expect(roundTrip(t.uuid.nullable, null)).toBe(null); expect(roundTrip(t.mongoId.nullable, undefined)).toBe(null); expect(roundTrip(t.mongoId.nullable, null)).toBe(null); expect(roundTrip(t.map(t.any).nullable, undefined)).toBe(null); expect(roundTrip(t.map(t.any).nullable, null)).toBe(null); expect(roundTrip(t.array(t.any).nullable, undefined)).toBe(null); expect(roundTrip(t.array(t.any).nullable, null)).toBe(null); expect(roundTrip(t.partial({ a: t.string }).nullable, undefined)).toBe(null); expect(roundTrip(t.partial({ a: t.string }).nullable, null)).toBe(null); expect(roundTrip(t.union(t.string).nullable, undefined)).toBe(null); expect(roundTrip(t.union(t.string).nullable, null)).toBe(null); expect(roundTrip(t.literal('a').nullable, undefined)).toBe('a'); expect(roundTrip(t.literal('a').nullable, null)).toBe(null); expect(roundTrip(t.enum(MyEnum).nullable, undefined)).toBe(null); expect(roundTrip(t.enum(MyEnum).nullable, null)).toBe(null); expect(roundTrip(t.type(Model).nullable, undefined)).toBe(null); expect(roundTrip(t.type(Model).nullable, null)).toBe(null); expect(roundTrip(t.type(ArrayBuffer).nullable, undefined)).toBe(null); expect(roundTrip(t.type(ArrayBuffer).nullable, null)).toBe(null); expect(roundTrip(t.type(Uint8Array).nullable, undefined)).toBe(null); expect(roundTrip(t.type(Uint8Array).nullable, null)).toBe(null); }); test('constructor argument', () => { class Product { @t id: number = 0; constructor(@t public title: string) { } } class Purchase { @t id: number = 0; constructor(@t public product: Product) { } } { const item = roundTrip(Purchase, { id: 4, product: new Product('asd') }); expect(item.product).toBeInstanceOf(Product); } }); test('omit circular reference 1', () => { class Model { @t another?: Model; constructor( @t public id: number = 0 ) { } } expect(getClassSchema(Model).hasCircularReference()).toBe(true); { const model = new Model(1); const model2 = new Model(2); model.another = model2; const plain = serializeToJson(Model, model); expect(plain.another).toBeInstanceOf(Object); expect(plain.another!.id).toBe(2); } { const model = new Model(1); model.another = model; const plain = serializeToJson(Model, model); expect(plain.another).toBe(undefined); } }); test('omit circular reference 2', () => { class Config { constructor(@t.type(() => Model) public model: any) { } } class Model { @t id: number = 0; @t config?: Config; } expect(getClassSchema(Model).hasCircularReference()).toBe(true); expect(getClassSchema(Config).hasCircularReference()).toBe(true); { const model = new Model; const config = new Config(model); model.config = config; const plain = serializeToJson(Model, model); expect(plain.config).toBeInstanceOf(Object); expect(plain.config!.model).toBe(undefined); } { const model = new Model; const model2 = new Model; const config = new Config(model2); model.config = config; const plain = serializeToJson(Model, model); expect(plain.config).toBeInstanceOf(Object); expect(plain.config!.model).toBeInstanceOf(Object); } }); test('omit circular reference 3', () => { class User { @t id: number = 0; @t.array(() => Image) public images: Image[] = []; constructor(@t public name: string) { } } class Image { @t id: number = 0; constructor( @t public user: User, @t public title: string, ) { if (user.images && !user.images.includes(this)) { user.images.push(this); } } } expect(getClassSchema(User).hasCircularReference()).toBe(true); expect(getClassSchema(Image).hasCircularReference()).toBe(true); { const user = new User('foo'); const image = new Image(user, 'bar'); { const plain = serializeToJson(User, user); expect(plain.images.length).toBe(1); expect(plain.images[0]).toBeInstanceOf(Object); expect(plain.images[0].title).toBe('bar'); } { const plain = serializeToJson(Image, image); expect(plain.user).toBeInstanceOf(Object); expect(plain.user.name).toBe('foo'); } } { const user = new User('foo'); const plain = serializeToJson(User, user); expect(plain.images.length).toBe(0); } });