Skip to content

Commit

Permalink
Merge pull request #1 from Vehmloewff/json-overhaul
Browse files Browse the repository at this point in the history
Json Overhaul
  • Loading branch information
Vehmloewff authored Sep 13, 2023
2 parents 151a220 + 6549feb commit 1f1bb6c
Show file tree
Hide file tree
Showing 8 changed files with 554 additions and 14 deletions.
27 changes: 27 additions & 0 deletions cbor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { cborDecode, cborEncode } from './cbor.ts'
import { asserts } from './deps.ts'

Deno.test('cborDecode decodes what cborEncode encoded', () => {
const value = {
foo: {
bar: true,
bin: 'baz',
fatty: 8123,
acid: .3,
},
data: null,
}

asserts.assertEquals(cborDecode(cborEncode(value)), value)

// Can also encode/decode primitives
asserts.assertEquals(cborDecode(cborEncode(true)), true)
asserts.assertEquals(cborDecode(cborEncode('asd')), 'asd')
asserts.assertEquals(cborDecode(cborEncode(123)), 123)
asserts.assertEquals(cborDecode(cborEncode(.123)), .123)
asserts.assertEquals(cborDecode(cborEncode(null)), null)
})

Deno.test('cborDecode throws if it can\'t decode', () => {
asserts.assertThrows(() => cborDecode(new Uint8Array([12, 2, 55, 90, 123])))
})
9 changes: 9 additions & 0 deletions cbor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { cbor } from './deps.ts'

export function cborEncode(value: unknown): Uint8Array {
return cbor.encode(value)
}

export function cborDecode(bytes: Uint8Array): unknown {
return cbor.decode(bytes)
}
2 changes: 2 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export * as base64 from 'https://deno.land/std@0.201.0/encoding/base64.ts'
export * as jwtCore from 'https://deno.land/x/djwt@v2.8/mod.ts'
export * as streamUtils from 'https://deno.land/std@0.201.0/streams/mod.ts'
export * as hexEncodingUtils from 'https://deno.land/std@0.201.0/encoding/hex.ts'

export * as cbor from 'https://deno.land/x/cbor@v1.5.4/index.js'
1 change: 1 addition & 0 deletions http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class ExpectantQuery {
}
}

/** @deprecated Use `SafeUnknown` instead */
export class JsonBody {
body: unknown

Expand Down
29 changes: 29 additions & 0 deletions json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { asserts } from './deps.ts'
import { jsonDecode, jsonEncode } from './json.ts'

Deno.test('jsonDecode decodes what jsonEncode encodes', () => {
const value = {
foo: {
bar: true,
bin: 'baz',
fatty: 8123,
acid: .3,
},
data: null,
}

asserts.assertEquals(jsonDecode(jsonEncode(value)), value)

// Can also encode/decode primitives
asserts.assertEquals(jsonDecode(jsonEncode(true)), true)
asserts.assertEquals(jsonDecode(jsonEncode('asd')), 'asd')
asserts.assertEquals(jsonDecode(jsonEncode(123)), 123)
asserts.assertEquals(jsonDecode(jsonEncode(.123)), .123)
asserts.assertEquals(jsonDecode(jsonEncode(null)), null)
})

Deno.test('jsonDecode throws if it can\'t decode', () => {
asserts.assertThrows(() => jsonDecode('duh'))
asserts.assertThrows(() => jsonDecode(''))
asserts.assertThrows(() => jsonDecode('{ new: }'))
})
76 changes: 62 additions & 14 deletions json.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// deno-lint-ignore no-explicit-any
export type Json = any

/** @deprecated Use `SafeUnknown` instead of validators */
export interface StringDescriptor {
type: 'string'
values?: string[]
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface NumberDescriptor {
type: 'number'
min?: number
Expand All @@ -16,21 +18,25 @@ export interface NumberDescriptor {
canBeNaN?: boolean
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface NullDescriptor {
type: 'null'
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface BooleanDescriptor {
type: 'boolean'
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface ArrayDescriptor {
type: 'array'
keyType: JsonDescriptor
maxLength?: number
minLength?: number
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface ObjectDescriptor {
type: 'object'
/** Either `keys` or `valueType` must be specified */
Expand All @@ -44,15 +50,18 @@ export interface ObjectDescriptor {
valueType?: JsonDescriptor
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface AnyDescriptor {
type: 'any'
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface TypeChoiceDescriptor {
type: 'choice'
options: JsonDescriptor[]
}

/** @deprecated Use `SafeUnknown` instead of validators */
export type JsonDescriptor =
| NullDescriptor
| StringDescriptor
Expand All @@ -63,21 +72,26 @@ export type JsonDescriptor =
| AnyDescriptor
| TypeChoiceDescriptor

/** @deprecated Use `SafeUnknown` instead of validators */
export interface ValidatorError {
message: string
path: string
}

/** @deprecated Use `SafeUnknown` instead of validators */
export interface ValidatorResultOk {
ok: true
}
/** @deprecated Use `SafeUnknown` instead of validators */
export interface ValidatorResultNotOk {
ok: false
errors: ValidatorError[]
}

/** @deprecated Use `SafeUnknown` instead of validators */
export type ValidatorResult = ValidatorResultOk | ValidatorResultNotOk

/** @deprecated Use `SafeUnknown` instead of validators */
export function validateJson(descriptor: JsonDescriptor, json: Json): ValidatorResult {
interface ValidatorResultOk {
ok: true
Expand Down Expand Up @@ -270,32 +284,66 @@ export function validateJson(descriptor: JsonDescriptor, json: Json): ValidatorR
}
}

/** @deprecated Will be removed in next major release. Use `jsonDecode` instead */
// deno-lint-ignore ban-types
export function jsonParse(string: string, fallback: {} | [] | null = null): Json {
if (!string.length) return string
if (string.startsWith('"') && string.endsWith('"')) return string.slice(1, -1)
if (string === 'true') return true
if (string === 'false') return false
if (string === 'null' || string === 'undefined') return null
if ((string.startsWith('{') && string.endsWith('}')) || (string.startsWith('[') && string.endsWith(']'))) {
export function jsonParse(json: string, fallback: {} | [] | null = null): Json {
if (!json.length) return json
if (json.startsWith('"') && json.endsWith('"')) return json.slice(1, -1)
if (json === 'true') return true
if (json === 'false') return false
if (json === 'null' || json === 'undefined') return null
if ((json.startsWith('{') && json.endsWith('}')) || (json.startsWith('[') && json.endsWith(']'))) {
try {
return JSON.parse(string)
return JSON.parse(json)
} catch (e) {
if (!fallback) {
e.raw = string
e.raw = json
throw e
}
console.warn(`Failed to parse JSON. Resorting to fallback. DUMP:`, e, string)
console.warn(`Failed to parse JSON. Resorting to fallback. DUMP:`, e, json)
return fallback
}
}

const numTry = Number(string)
const numTry = Number(json)
if (!isNaN(numTry)) return numTry

return string
return json
}

export function jsonStringify(json: Json, spacer = ''): string {
return JSON.stringify(json, undefined, spacer)
/** @deprecated Will be removed in next major release. Use `jsonEncode` instead */
export function jsonStringify(data: Json, spacer = ''): string {
return JSON.stringify(data, undefined, spacer)
}

/**
* Parse json.
*
* Wraps the native implementation, the difference being that primitives are parsed at the top level
* (i.e. `jsonParse("null")`) is valid */
export function jsonDecode(json: string): unknown {
json = json.trim()

if (!json.length) throw new Error('Expected a json primitive, object, or array, but found nothing')
if (json.startsWith('"') && json.endsWith('"')) return json.slice(1, -1)
if (json === 'true') return true
if (json === 'false') return false
if (json === 'null' || json === 'undefined') return null
if ((json.startsWith('{') && json.endsWith('}')) || (json.startsWith('[') && json.endsWith(']'))) {
try {
return JSON.parse(json)
} catch (error) {
throw new Error(`Failed to parse json. ${error.message} ... Dump: "${json}"`)
}
}

const numTry = Number(json)
if (!isNaN(numTry)) return numTry

throw new Error(`Expected a json primitive, object, or array, but found: "${json}"`)
}

/** Stringify json with an optional spacer. Wraps the native implementation */
export function jsonEncode(data: unknown, spacer = ''): string {
return JSON.stringify(data, undefined, spacer)
}
Loading

0 comments on commit 1f1bb6c

Please sign in to comment.