Skip to content

Commit

Permalink
Merge pull request #889 from jan-molak/features/to-json
Browse files Browse the repository at this point in the history
feat(object): toJSON available as a standalone function
  • Loading branch information
jan-molak authored Aug 14, 2024
2 parents 61ca85f + 82f2da6 commit db63059
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
node-version: [ 16.x, 18.x, 20.x ]
node-version: [ 16.x, 18.x, 20.x, 22.x ]

steps:
- uses: actions/checkout@v4
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
},
"homepage": "https://jan-molak.github.io/tiny-types/",
"engines": {
"node": "^16 || ^18 || ^20",
"npm": "^8 || ^9 || ^10"
"node": "^16 || ^18 || ^20 || ^22"
},
"devDependencies": {
"@types/chai": "4.3.17",
Expand Down
69 changes: 2 additions & 67 deletions src/TinyType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ensure } from './ensure';
import { equal, isRecord, significantFieldsOf, stringify } from './objects';
import { equal, significantFieldsOf, stringify, toJSON } from './objects';
import { isDefined } from './predicates';
import { JSONObject, JSONValue, Serialisable } from './types';
import { JSONValue, Serialisable } from './types';

/**
* @desc The {@link TinyTypeOf} can be used to define simple
Expand Down Expand Up @@ -137,68 +137,3 @@ export abstract class TinyType implements Serialisable {
}, {}) as JSONValue;
}
}

function toJSON(value: any): JSONValue | undefined {
switch (true) {
case value && !! value.toJSON:
return value.toJSON();
case value && Array.isArray(value):
return value.map(v => {
return v === undefined
? null
: toJSON(v) as JSONValue;
});
case value && value instanceof Map:
return mapToJSON(value);
case value && value instanceof Set:
return toJSON(Array.from(value));
case value && isRecord(value):
return recordToJSON(value);
case value && value instanceof Error:
return errorToJSON(value);
case isSerialisablePrimitive(value):
return value;
default:
return JSON.stringify(value);
}
}

function mapToJSON(map: Map<any, any>): JSONObject {
const serialised = Array.from(map, ([key, value]) => [ toJSON(key), toJSON(value) ]);

return Object.fromEntries(serialised);
}

function recordToJSON(value: Record<any, any>): JSONObject {
const serialised = Object.entries(value)
.map(([ k, v ]) => [ toJSON(k), toJSON(v) ]);

return Object.fromEntries(serialised);
}

function errorToJSON(value: Error): JSONObject {
return Object.getOwnPropertyNames(value)
.reduce((serialised, key) => {
serialised[key] = toJSON(value[key])
return serialised;
}, { }) as JSONObject;
}

function isSerialisableNumber(value: unknown): value is number {
return typeof value === 'number'
&& ! Number.isNaN(value)
&& value !== Number.NEGATIVE_INFINITY
&& value !== Number.POSITIVE_INFINITY;
}

function isSerialisablePrimitive(value: unknown): value is string | boolean | number | null | undefined {
if (['string', 'boolean'].includes(typeof value)) {
return true;
}

if (value === null || value === undefined) {
return true;
}

return isSerialisableNumber(value);
}
1 change: 1 addition & 0 deletions src/objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './isObject';
export * from './isRecord';
export * from './significantFields';
export * from './stringify';
export * from './toJSON';
73 changes: 73 additions & 0 deletions src/objects/toJSON.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable unicorn/filename-case */
import { JSONObject, JSONValue } from '../types';
import { isRecord } from './isRecord';

/**
* Serialises the object to a JSON representation.
*
* @param value
*/
export function toJSON(value: any): JSONValue | undefined { // eslint-disable-line @typescript-eslint/explicit-module-boundary-types
switch (true) {
case value && !! value.toJSON:
return value.toJSON();
case value && Array.isArray(value):
return value.map(v => {
return v === undefined
? null
: toJSON(v) as JSONValue;
});
case value && value instanceof Map:
return mapToJSON(value);
case value && value instanceof Set:
return toJSON(Array.from(value));
case value && isRecord(value):
return recordToJSON(value);
case value && value instanceof Error:
return errorToJSON(value);
case isSerialisablePrimitive(value):
return value;
default:
return JSON.stringify(value);
}
}

function mapToJSON(map: Map<any, any>): JSONObject {
const serialised = Array.from(map, ([key, value]) => [ toJSON(key), toJSON(value) ]);

return Object.fromEntries(serialised);
}

function recordToJSON(value: Record<any, any>): JSONObject {
const serialised = Object.entries(value)
.map(([ k, v ]) => [ toJSON(k), toJSON(v) ]);

return Object.fromEntries(serialised);
}

function errorToJSON(value: Error): JSONObject {
return Object.getOwnPropertyNames(value)
.reduce((serialised, key) => {
serialised[key] = toJSON(value[key])
return serialised;
}, { }) as JSONObject;
}

function isSerialisableNumber(value: unknown): value is number {
return typeof value === 'number'
&& ! Number.isNaN(value)
&& value !== Number.NEGATIVE_INFINITY
&& value !== Number.POSITIVE_INFINITY;
}

function isSerialisablePrimitive(value: unknown): value is string | boolean | number | null | undefined {
if (['string', 'boolean'].includes(typeof value)) {
return true;
}

if (value === null || value === undefined) {
return true;
}

return isSerialisableNumber(value);
}

0 comments on commit db63059

Please sign in to comment.