Browse Source

Add serializer types

Include JSON and XML serializers.
master
TheoryOfNekomata 1 year ago
parent
commit
c96b99b4a0
3 changed files with 184 additions and 1 deletions
  1. +1
    -1
      .gitignore
  2. +30
    -0
      src/types/application/json.ts
  3. +153
    -0
      src/types/application/xml.ts

+ 1
- 1
.gitignore View File

@@ -105,5 +105,5 @@ dist
.tern-port

.npmrc
types/
/types/
.idea/

+ 30
- 0
src/types/application/json.ts View File

@@ -0,0 +1,30 @@
import { TypeOptions } from 'src/common';

interface CommonApplicationJsonOptions extends TypeOptions {
type: 'application/json';
}

interface SerializeApplicationJsonOptions extends CommonApplicationJsonOptions {
indent?: number;
replacer?: (key: string, value: unknown) => unknown;
}

export const serialize = async <T, O extends TypeOptions = SerializeApplicationJsonOptions>(
data: T,
options = {} as O,
): Promise<string> => {
const theOptions = options as SerializeApplicationJsonOptions;
return Promise.resolve(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>(
serialized: string,
options = {} as O,
): Promise<T> => {
const theOptions = options as DeserializeApplicationJsonOptions;
return Promise.resolve(JSON.parse(serialized, theOptions.reviver) as T);
};

+ 153
- 0
src/types/application/xml.ts View File

@@ -0,0 +1,153 @@
import type { Element } from 'xml-js';
import { TypeOptions } from 'src/common';

interface CommonApplicationXmlOptions extends TypeOptions {
type: 'application/xml';
}

interface SerializeApplicationXmlOptions extends CommonApplicationXmlOptions {
indent?: number | string;
rootElementName?: string;
}

const constructElement = (data: unknown, name: string): Element[] => {
if (Array.isArray(data)) {
return [
{
type: 'element',
name,
attributes: { array: 'array' },
elements: data
.flatMap((item, i) => constructElement(item, `_${i}`)),
},
];
}

if (
typeof data === 'string'
|| typeof data === 'number'
|| typeof data === 'boolean'
) {
return [
{
name,
type: 'element',
attributes: { type: typeof data },
elements: [
{
type: 'text',
text: data.toString(),
},
],
},
];
}

if (typeof data === 'object' && data !== null) {
return [
{
name,
type: 'element',
attributes: { type: 'object' },
elements: Object.entries(data)
.flatMap(([key, value]) => constructElement(value, key)),
},
];
}

return [];
};

let parseElement: (element: Element, allowValueOverride?: boolean) => unknown;

const parseObjectElement = (element: Element): unknown => Object.fromEntries(
element.elements?.map((e) => [e.name, parseElement(e)]) ?? [],
);

const parseNumberElement = (element: Element, allowValueOverride = false): unknown => {
if (allowValueOverride) {
return Number(element.elements?.[0].text) || Number(element.attributes?.value);
}
return Number(element.elements?.[0].text);
};

const parseBooleanElement = (element: Element, allowValueOverride = false): unknown => {
const stringValue = (
allowValueOverride
? (element.elements?.[0].text ?? element.attributes?.value)
: element.elements?.[0].text
)
?.toString()
.toLowerCase();

if (stringValue === 'true') {
return true;
}

if (stringValue === 'false') {
return false;
}

return undefined;
};

parseElement = (element: Element, allowValueOverride = false): unknown => {
if (element.attributes && 'array' in element.attributes) {
return element.elements?.map((e) => parseElement(e, allowValueOverride));
}

if (element.attributes?.type === 'object') {
return parseObjectElement(element);
}

if (element.attributes?.type === 'string') {
return element.elements?.[0].text;
}

if (element.attributes?.type === 'number') {
return parseNumberElement(element, allowValueOverride);
}

if (element.attributes?.type === 'boolean') {
return parseBooleanElement(element, allowValueOverride);
}

return (
allowValueOverride
? (element.elements?.[0].text ?? element.attributes?.value)
: element.elements?.[0].text
);
};

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

return js2xml(
{ elements: constructElement(data, rootElementName) },
{
compact: false,
spaces: indent ?? 0,
},
);
};

interface DeserializeApplicationXmlOptions extends CommonApplicationXmlOptions {
allowValueOverride?: boolean;
}

export const deserialize = async <T, O extends TypeOptions = DeserializeApplicationXmlOptions>(
serialized: string,
options = {} as O,
): Promise<T> => {
const { xml2js } = await import('xml-js');
const { allowValueOverride } = options as DeserializeApplicationXmlOptions;
const rawElements = xml2js(serialized, {
compact: false,
nativeType: false,
}) as Element;
return parseElement(rawElements.elements?.[0] ?? {}, allowValueOverride) as T;
};

Loading…
Cancel
Save