Browse Source

Update serializers

Make de/serializer functions sync, and add URL-encoded form type.
master
TheoryOfNekomata 1 year ago
parent
commit
de7ddd31e6
9 changed files with 146 additions and 83 deletions
  1. +18
    -1
      package.json
  2. +2
    -2
      src/common.ts
  3. +17
    -19
      src/index.ts
  4. +6
    -6
      src/types/application/json.ts
  5. +16
    -0
      src/types/application/x-www-form-urlencoded.ts
  6. +5
    -7
      src/types/application/xml.ts
  7. +24
    -24
      test/application/json.test.ts
  8. +34
    -0
      test/application/x-www-form-urlencoded.test.ts
  9. +24
    -24
      test/application/xml.test.ts

+ 18
- 1
package.json View File

@@ -1,5 +1,5 @@
{
"name": "@modal-sh/oatmeal",
"name": "@modal-sh/oatmeal-core",
"version": "0.0.0",
"files": [
"dist",
@@ -48,5 +48,22 @@
},
"dependencies": {
"xml-js": "^1.6.11"
},
"types": "./dist/types/index.d.ts",
"main": "./dist/cjs/production/index.js",
"module": "./dist/esm/production/index.js",
"exports": {
".": {
"development": {
"require": "./dist/cjs/development/index.js",
"import": "./dist/esm/development/index.js"
},
"require": "./dist/cjs/production/index.js",
"import": "./dist/esm/production/index.js",
"types": "./dist/types/index.d.ts"
}
},
"typesVersions": {
"*": {}
}
}

+ 2
- 2
src/common.ts View File

@@ -3,6 +3,6 @@ export interface TypeOptions {
}

export interface SerializeType<Options extends TypeOptions = TypeOptions> {
serialize: <T>(data: T, options: Options) => Promise<string>;
deserialize: <T>(serialized: string, options: Options) => Promise<T>;
serialize: <T>(data: T, options: Options) => string;
deserialize: <T>(serialized: string, options: Options) => T;
}

+ 17
- 19
src/index.ts View File

@@ -1,40 +1,38 @@
import { TypeOptions, SerializeType } from 'src/common';
import * as applicationJson from 'src/types/application/json';
import * as applicationXml from 'src/types/application/xml';
import * as applicationXWwwFormUrlencoded from 'src/types/application/x-www-form-urlencoded';

interface LoadSerializeModule<T extends SerializeType> {
(): Promise<T>;
}

const TYPES: Record<string, LoadSerializeModule<SerializeType>> = {
'application/json': () => import('src/types/application/json'),
'text/json': () => import('src/types/application/json'),
'application/xml': () => import('src/types/application/xml'),
'text/xml': () => import('src/types/application/xml'),
const TYPES: Record<string, SerializeType> = {
'application/json': applicationJson,
'text/json': applicationJson,
'application/xml': applicationXml,
'text/xml': applicationXml,
'application/x-www-form-urlencoded': applicationXWwwFormUrlencoded,
};

export const AVAILABLE_TYPES: (keyof typeof TYPES)[] = Object.keys(TYPES);

export const serialize = async <T, O extends TypeOptions = TypeOptions>(
export const serialize = <T, O extends TypeOptions = TypeOptions>(
data: T,
options: O,
): Promise<string> => {
): string => {
const { type: optionsType } = options;
const { [optionsType]: loadType } = TYPES;
if (!loadType) {
const { [optionsType]: serializeType } = TYPES;
if (!serializeType) {
throw new Error(`Unsupported type: ${optionsType}`);
}
const serializeType = await loadType();
return serializeType.serialize(data, options);
};

export const deserialize = async <T, O extends TypeOptions = TypeOptions>(
export const deserialize = <T, O extends TypeOptions = TypeOptions>(
serialized: string,
options: O,
): Promise<T> => {
): T => {
const { type: optionsType } = options;
const { [optionsType]: loadType } = TYPES;
if (!loadType) {
const { [optionsType]: serializeType } = TYPES;
if (!serializeType) {
throw new Error(`Unsupported type: ${optionsType}`);
}
const serializeType = await loadType();
return serializeType.deserialize<T>(serialized, options);
};

+ 6
- 6
src/types/application/json.ts View File

@@ -9,22 +9,22 @@ interface SerializeApplicationJsonOptions extends CommonApplicationJsonOptions {
replacer?: (key: string, value: unknown) => unknown;
}

export const serialize = async <T, O extends TypeOptions = SerializeApplicationJsonOptions>(
export const serialize = <T, O extends TypeOptions = SerializeApplicationJsonOptions>(
data: T,
options = {} as O,
): Promise<string> => {
): string => {
const theOptions = options as SerializeApplicationJsonOptions;
return Promise.resolve(JSON.stringify(data, theOptions.replacer, theOptions.indent));
return JSON.stringify(data, theOptions.replacer, theOptions.indent);
};

interface DeserializeApplicationJsonOptions extends CommonApplicationJsonOptions {
reviver?: (key: string, value: unknown) => unknown;
}

export const deserialize = async <T, O extends TypeOptions = DeserializeApplicationJsonOptions>(
export const deserialize = <T, O extends TypeOptions = DeserializeApplicationJsonOptions>(
serialized: string,
options = {} as O,
): Promise<T> => {
): T => {
const theOptions = options as DeserializeApplicationJsonOptions;
return Promise.resolve(JSON.parse(serialized, theOptions.reviver) as T);
return JSON.parse(serialized, theOptions.reviver) as T;
};

+ 16
- 0
src/types/application/x-www-form-urlencoded.ts View File

@@ -0,0 +1,16 @@
export const serialize = <T>(
data: T,
): string => {
if (typeof data !== 'object') {
throw new TypeError('Data must be an object');
}
return new URLSearchParams(data as Record<string, string>).toString();
}

export const deserialize = <T>(
serialized: string,
): T => {
return Object.fromEntries(
new URLSearchParams(serialized).entries()
) as unknown as T;
}

+ 5
- 7
src/types/application/xml.ts View File

@@ -1,4 +1,4 @@
import type { Element } from 'xml-js';
import { js2xml, xml2js, type Element } from 'xml-js';
import { TypeOptions } from 'src/common';

interface CommonApplicationXmlOptions extends TypeOptions {
@@ -119,11 +119,10 @@ parseElement = (element: Element, allowValueOverride = false): unknown => {
);
};

export const serialize = async <T, O extends TypeOptions = SerializeApplicationXmlOptions>(
export const serialize = <T, O extends TypeOptions = SerializeApplicationXmlOptions>(
data: T,
options = {} as O,
): Promise<string> => {
const { js2xml } = await import('xml-js');
): string => {
const { indent, rootElementName = 'root' } = options as SerializeApplicationXmlOptions;

return js2xml(
@@ -139,11 +138,10 @@ interface DeserializeApplicationXmlOptions extends CommonApplicationXmlOptions {
allowValueOverride?: boolean;
}

export const deserialize = async <T, O extends TypeOptions = DeserializeApplicationXmlOptions>(
export const deserialize = <T, O extends TypeOptions = DeserializeApplicationXmlOptions>(
serialized: string,
options = {} as O,
): Promise<T> => {
const { xml2js } = await import('xml-js');
): T => {
const { allowValueOverride } = options as DeserializeApplicationXmlOptions;
const rawElements = xml2js(serialized, {
compact: false,


+ 24
- 24
test/application/json.test.ts View File

@@ -3,65 +3,65 @@ import { deserialize, serialize } from '../../src/types/application/json';

describe('application/json', () => {
describe('serialize', () => {
it('should serialize a string', async () => {
const result = await serialize('Hello, World!', { type: 'application/json' });
it('should serialize a string', () => {
const result = serialize('Hello, World!', { type: 'application/json' });
expect(result).toBe('"Hello, World!"');
});

it('should serialize a number', async () => {
const result = await serialize(123, { type: 'application/json' });
it('should serialize a number', () => {
const result = serialize(123, { type: 'application/json' });
expect(result).toBe('123');
});

it('should serialize a boolean', async () => {
const result = await serialize(true, { type: 'application/json' });
it('should serialize a boolean', () => {
const result = serialize(true, { type: 'application/json' });
expect(result).toBe('true');
});

it('should serialize an array', async () => {
const result = await serialize([1, 2, 3], { type: 'application/json' });
it('should serialize an array', () => {
const result = serialize([1, 2, 3], { type: 'application/json' });
expect(result).toBe('[1,2,3]');
});

it('should serialize an object', async () => {
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json' });
it('should serialize an object', () => {
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json' });
expect(result).toBe('{"a":1,"b":2,"c":3}');
});

it('should serialize an object with indent', async () => {
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json', indent: 2 });
it('should serialize an object with indent', () => {
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json', indent: 2 });
expect(result).toBe('{\n "a": 1,\n "b": 2,\n "c": 3\n}');
});
});

describe('deserialize', () => {
it('should deserialize a string', async () => {
const result = await deserialize<string>('"Hello, World!"', { type: 'application/json' });
it('should deserialize a string', () => {
const result = deserialize<string>('"Hello, World!"', { type: 'application/json' });
expect(result).toBe('Hello, World!');
});

it('should deserialize a number', async () => {
const result = await deserialize<number>('123', { type: 'application/json' });
it('should deserialize a number', () => {
const result = deserialize<number>('123', { type: 'application/json' });
expect(result).toBe(123);
});

it('should deserialize a boolean', async () => {
const result = await deserialize<boolean>('true', { type: 'application/json' });
it('should deserialize a boolean', () => {
const result = deserialize<boolean>('true', { type: 'application/json' });
expect(result).toBe(true);
});

it('should deserialize an array', async () => {
const result = await deserialize<number[]>('[1,2,3]', { type: 'application/json' });
it('should deserialize an array', () => {
const result = deserialize<number[]>('[1,2,3]', { type: 'application/json' });
expect(result).toEqual([1, 2, 3]);
});

it('should deserialize an object', async () => {
const result = await deserialize<{ a: number, b: number, c: number }>('{"a":1,"b":2,"c":3}', { type: 'application/json' });
it('should deserialize an object', () => {
const result = deserialize<{ a: number, b: number, c: number }>('{"a":1,"b":2,"c":3}', { type: 'application/json' });
expect(result).toEqual({ a: 1, b: 2, c: 3 });
});

it('should deserialize an object with indent', async () => {
const result = await deserialize<{ a: number, b: number, c: number }>(
it('should deserialize an object with indent', () => {
const result = deserialize<{ a: number, b: number, c: number }>(
'{\n "a": 1,\n "b": 2,\n "c": 3\n}',
{ type: 'application/json' },
);


+ 34
- 0
test/application/x-www-form-urlencoded.test.ts View File

@@ -0,0 +1,34 @@
import { describe, expect, it } from 'vitest';
import { deserialize, serialize } from '../../src/types/application/x-www-form-urlencoded';

describe('application/x-www-form-urlencoded', () => {
describe('serialize', () => {
it('should throw when serializing a string', () => {
expect(() => serialize('Hello, World!')).toThrow();
});

it('should throw when serializing a number', () => {
expect(() => serialize(123)).toThrow();
});

it('should throw when serializing a boolean', () => {
expect(() => serialize(true)).toThrow();
});

it('should throw when serializing an array', () => {
expect(() => serialize([1, 2, 3])).toThrow();
});

it('should serialize an object', () => {
const result = serialize({ a: 1, b: 2, c: 3 });
expect(result).toBe('a=1&b=2&c=3');
});
});

describe('deserialize', () => {
it('should deserialize an object', () => {
const result = deserialize<{ a: string, b: string, c: string }>('a=1&b=2&c=3');
expect(result).toEqual({ a: '1', b: '2', c: '3' });
});
});
});

+ 24
- 24
test/application/xml.test.ts View File

@@ -3,38 +3,38 @@ import { serialize, deserialize } from '../../src/types/application/xml';

describe('application/xml', () => {
describe('serialize', () => {
it('should serialize a string', async () => {
const result = await serialize('Hello, World!', { type: 'application/xml' });
it('should serialize a string', () => {
const result = serialize('Hello, World!', { type: 'application/xml' });
expect(result).toBe('<root type="string">Hello, World!</root>');
});

it('should serialize a number', async () => {
const result = await serialize(123, { type: 'application/xml' });
it('should serialize a number', () => {
const result = serialize(123, { type: 'application/xml' });
expect(result).toBe('<root type="number">123</root>');
});

it('should serialize a boolean', async () => {
const result = await serialize(true, { type: 'application/xml' });
it('should serialize a boolean', () => {
const result = serialize(true, { type: 'application/xml' });
expect(result).toBe('<root type="boolean">true</root>');
});

it('should serialize an array', async () => {
const result = await serialize([1, 2, 3], { type: 'application/xml' });
it('should serialize an array', () => {
const result = serialize([1, 2, 3], { type: 'application/xml' });
expect(result).toBe('<root array="array"><_0 type="number">1</_0><_1 type="number">2</_1><_2 type="number">3</_2></root>');
});

it('should serialize an object', async () => {
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml' });
it('should serialize an object', () => {
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml' });
expect(result).toBe('<root type="object"><a type="number">1</a><b type="number">2</b><c type="number">3</c></root>');
});

it('should serialize an object with a custom root element name', async () => {
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', rootElementName: 'fsh', });
it('should serialize an object with a custom root element name', () => {
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', rootElementName: 'fsh', });
expect(result).toBe('<fsh type="object"><a type="number">1</a><b type="number">2</b><c type="number">3</c></fsh>');
});

it('should serialize an object with indent', async () => {
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', indent: 2 });
it('should serialize an object with indent', () => {
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', indent: 2 });
expect(result).toBe(`<root type="object">
<a type="number">1</a>
<b type="number">2</b>
@@ -44,28 +44,28 @@ describe('application/xml', () => {
});

describe('deserialize', () => {
it('should deserialize a string', async () => {
const result = await deserialize<string>('<root type="string">Hello, World!</root>', { type: 'application/xml' });
it('should deserialize a string', () => {
const result = deserialize<string>('<root type="string">Hello, World!</root>', { type: 'application/xml' });
expect(result).toBe('Hello, World!');
});

it('should deserialize a number', async () => {
const result = await deserialize<number>('<root type="number">123</root>', { type: 'application/xml' });
it('should deserialize a number', () => {
const result = deserialize<number>('<root type="number">123</root>', { type: 'application/xml' });
expect(result).toBe(123);
});

it('should deserialize a boolean', async () => {
const result = await deserialize<boolean>('<root type="boolean">true</root>', { type: 'application/xml' });
it('should deserialize a boolean', () => {
const result = deserialize<boolean>('<root type="boolean">true</root>', { type: 'application/xml' });
expect(result).toBe(true);
});

it('should deserialize an array', async () => {
const result = await deserialize<number[]>('<root array="array"><_0 type="number">1</_0><_1 type="number">2</_1><_2 type="number">3</_2></root>', { type: 'application/xml' });
it('should deserialize an array', () => {
const result = deserialize<number[]>('<root array="array"><_0 type="number">1</_0><_1 type="number">2</_1><_2 type="number">3</_2></root>', { type: 'application/xml' });
expect(result).toEqual([1, 2, 3]);
});

it('should deserialize an object', async () => {
const result = await deserialize<{ a: number, b: number, c: number }>(
it('should deserialize an object', () => {
const result = deserialize<{ a: number, b: number, c: number }>(
'<root type="object"><a type="number">1</a><b type="number">2</b><c type="number">3</c></root>',
{ type: 'application/xml' },
);


Loading…
Cancel
Save