UNPKG

jasone

Version:

A lightweight, extensible JSON encoder and decoder that supports custom types.

1 lines 7.98 kB
var JasoneError=class extends Error{},UnknownTypeIdError=class extends JasoneError{constructor(typeId,value){super(`Unknown type id: ${typeId}`,{cause:value})}},NonJsonValueError=class extends JasoneError{constructor(value){super(`Non-JSON value received`,{cause:value})}},IllegalEncoderResultError=class extends JasoneError{constructor(value){super(`Illegal encoder result received: The encoder result cannot contains the type identifer.`,{cause:value})}},UnhandledValueError=class extends JasoneError{constructor(value){super(`Unhandled value received: No encoder found for this value.`,{cause:value})}},DuplicatedTypeIdError=class extends JasoneError{constructor(typeId,decoder){super(`Duplicated type id: The TypeId<${JSON.stringify(typeId)}> is already registered.`,{cause:decoder})}};let TypeIdRegistry=function(TypeIdRegistry$1){return TypeIdRegistry$1[TypeIdRegistry$1.Undefined=0]=`Undefined`,TypeIdRegistry$1[TypeIdRegistry$1.Date=1]=`Date`,TypeIdRegistry$1[TypeIdRegistry$1.BigInt=2]=`BigInt`,TypeIdRegistry$1[TypeIdRegistry$1.RegExp=3]=`RegExp`,TypeIdRegistry$1[TypeIdRegistry$1.Set=4]=`Set`,TypeIdRegistry$1[TypeIdRegistry$1.Map=5]=`Map`,TypeIdRegistry$1[TypeIdRegistry$1.URL=6]=`URL`,TypeIdRegistry$1}({});const bigIntTransformer={encoder:{filter:{bigint:!0},handler:({value})=>[TypeIdRegistry.BigInt,{bigint:value.toString()}]},decoder:{filter:TypeIdRegistry.BigInt,handler:({value})=>BigInt(value.bigint)}},dateTransformer={encoder:{filter:{class:Date,object:obj=>obj instanceof Date},handler:({value})=>[TypeIdRegistry.Date,{iso:value.toISOString()}]},decoder:{filter:TypeIdRegistry.Date,handler:({value})=>new Date(value.iso)}},mapTransformer={encoder:{filter:{class:Map,object:obj=>obj instanceof Map},handler:({value,jasone})=>[TypeIdRegistry.Map,{entries:value.entries().map(([key,val])=>[jasone.encode(key),jasone.encode(val)]).toArray()}]},decoder:{filter:TypeIdRegistry.Map,handler:({value,jasone})=>new Map(value.entries.map(([key,val])=>[jasone.decode(key),jasone.decode(val)]))}},regExpTransformer={encoder:{filter:{class:RegExp,object:obj=>obj instanceof RegExp},handler:({value})=>[TypeIdRegistry.RegExp,{source:value.source,flags:value.flags}]},decoder:{filter:TypeIdRegistry.RegExp,handler:({value})=>new RegExp(value.source,value.flags)}},setTransformer={encoder:{filter:{class:Set,object:obj=>obj instanceof Set},handler:({value,jasone})=>[TypeIdRegistry.Set,{values:value.values().map(entry=>jasone.encode(entry)).toArray()}]},decoder:{filter:TypeIdRegistry.Set,handler:({value,jasone})=>new Set(value.values.map(entry=>jasone.decode(entry)))}},undefinedTransformer={encoder:{filter:{undefined:!0},handler:()=>[TypeIdRegistry.Undefined,{}]},decoder:{filter:TypeIdRegistry.Undefined,handler:()=>void 0}},urlTransformer={encoder:{filter:{class:URL,object:obj=>obj instanceof URL},handler:({value})=>[TypeIdRegistry.URL,{url:value.toString()}]},decoder:{filter:TypeIdRegistry.URL,handler:({value})=>new URL(value.url)}},defaultTransformers=[undefinedTransformer,dateTransformer,bigIntTransformer,regExpTransformer,setTransformer,mapTransformer,urlTransformer],nonJsonTypes=[`bigint`,`function`,`object`,`symbol`,`undefined`],matchEncoderFilters=(filterInput,value)=>{if(!filterInput)return!0;let filters=Array.isArray(filterInput)?filterInput:[filterInput],type=typeof value;for(let filter of filters){if(filter.any?.(value)||type===`object`&&value!==null&&filter.class===value.constructor)return!0;for(let field of nonJsonTypes){if(type!==field)continue;let typeFilter=filter[field];if(typeFilter===!0||typeFilter?.(value))return!0}}return!1},matchDecoderFilters=(filterInput,value,typeId)=>{if(filterInput===void 0)return!0;let filters=Array.isArray(filterInput)?filterInput:[filterInput];for(let filter of filters)if(typeof filter==`function`&&filter(typeId,value)||filter===typeId)return!0;return!1};var Jasone=class Jasone{#typeIdentifier;#anyDecoder=[];#typeIdDecoder=new Map;#anyEncoder=[];#classEncoder=new Map;#customEncoder={bigint:[],function:[],object:[],symbol:[],undefined:[]};constructor(options={}){this.#typeIdentifier=options.typeIdentifier??`$`;for(let transformer of options.types??[])this.register(transformer)}#registerEncoder(encoder){let filters=encoder.filter?Array.isArray(encoder.filter)?encoder.filter:[encoder.filter]:[];filters.length===0&&this.#anyEncoder.push(encoder);for(let filter of filters){if(filter.any&&this.#anyEncoder.push(encoder),filter.class){let classList=this.#classEncoder.get(filter.class);classList||(classList=[],this.#classEncoder.set(filter.class,classList)),classList.push(encoder)}for(let field of Object.keys(filter))nonJsonTypes.includes(field)&&filter[field]&&this.#customEncoder[field].push(encoder)}}#registerDecoder(decoder){let filters=decoder.filter?Array.isArray(decoder.filter)?decoder.filter:[decoder.filter]:[];filters.length===0&&this.#anyDecoder.push(decoder);for(let filter of filters){if(typeof filter==`function`){this.#anyDecoder.push(decoder);continue}if(this.#typeIdDecoder.has(filter))throw new DuplicatedTypeIdError(filter,decoder);this.#typeIdDecoder.set(filter,decoder)}}register(transformer){transformer.encoder&&this.#registerEncoder(transformer.encoder),transformer.decoder&&this.#registerDecoder(transformer.decoder)}encode(value){let type=typeof value;switch(type){case`boolean`:case`number`:case`string`:return value;case`object`:if(value===null)return null;if(Array.isArray(value))return value.map(entry=>this.encode(entry));if(Object.getPrototypeOf(value)===Object.prototype){let encodedObject=Object.fromEntries(Object.entries(value).map(([key,inner])=>[key,this.encode(inner)]));return this.#typeIdentifier in encodedObject&&(encodedObject[this.#typeIdentifier]=[encodedObject[this.#typeIdentifier]]),encodedObject}}let encoder;if(type===`object`&&(encoder??=this.#classEncoder.get(value.constructor)?.find(inner=>matchEncoderFilters(inner.filter,value))),encoder??=this.#customEncoder[type].find(inner=>matchEncoderFilters(inner.filter,value)),encoder??=this.#anyEncoder.find(inner=>matchEncoderFilters(inner.filter,value)),!encoder)throw new UnhandledValueError(value);let[typeId,result]=encoder.handler({value,jasone:this});if(typeId!==null&&this.#typeIdentifier in result)throw new IllegalEncoderResultError(result);return typeId===null?result:{[this.#typeIdentifier]:typeId,...result}}decode(value){return this.#decode(value)}#decode(value,ignoreTypeIdentifier=!1){switch(typeof value){case`boolean`:case`number`:case`string`:return value;case`object`:if(value===null)return null;if(Array.isArray(value))return value.map(inner=>this.#decode(inner));if(this.#typeIdentifier in value&&!Array.isArray(value)&&!ignoreTypeIdentifier){let typeId=value[this.#typeIdentifier];if(Array.isArray(typeId)){let[escaped]=typeId,cloned={...value};return cloned[this.#typeIdentifier]=escaped,this.#decode(cloned,!0)}let decoder;if(decoder??=this.#typeIdDecoder.get(typeId),decoder??=this.#anyDecoder.find(entry=>matchDecoderFilters(entry.filter,value,typeId)),!decoder)throw new UnknownTypeIdError(typeId,value);return decoder.handler({typeId,value,jasone:this})}if(Object.getPrototypeOf(value)===Object.prototype)return Object.fromEntries(Object.entries(value).map(([key,inner])=>[key,this.#decode(inner)]));break}throw new NonJsonValueError(value)}serialize(value){return this.encode(value)}deserialize(value){return this.decode(value)}stringify(value){return JSON.stringify(this.encode(value))}parse(value){return this.decode(JSON.parse(value))}static default=new Jasone({types:defaultTransformers});static encode=Jasone.default.encode.bind(Jasone.default);static serialize=Jasone.default.serialize.bind(Jasone.default);static decode=Jasone.default.decode.bind(Jasone.default);static deserialize=Jasone.default.deserialize.bind(Jasone.default);static stringify=Jasone.default.stringify.bind(Jasone.default);static parse=Jasone.default.parse.bind(Jasone.default);static register=Jasone.default.register.bind(Jasone.default)};export{Jasone,defaultTransformers,nonJsonTypes};