Skip to content

Instantly share code, notes, and snippets.

@timdp
Created June 29, 2020 13:18
Show Gist options
  • Select an option

  • Save timdp/67d39ceed9a4cfb3e55c1fa6b9b0b745 to your computer and use it in GitHub Desktop.

Select an option

Save timdp/67d39ceed9a4cfb3e55c1fa6b9b0b745 to your computer and use it in GitHub Desktop.

Revisions

  1. timdp created this gist Jun 29, 2020.
    108 changes: 108 additions & 0 deletions serialize.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,108 @@
    const misleadingStrings = ['', 'true', 'false']
    const reWhitespaceStart = /^\s/
    const reWhitespaceEnd = /\s$/
    const reNumeric = /^\s*(?:[0-9]+|[0-9]*\.[0-9]+)\s*$/
    const indent = ' '
    const maxStringLength = 60
    const keysSorted = true

    const byKey = (a, b) => a[0].localeCompare(b[0])

    const stringRequiresQuotes = str =>
    misleadingStrings.includes(str) ||
    reWhitespaceStart.test(str) ||
    reWhitespaceEnd.test(str) ||
    reNumeric.test(str)

    const serializeNullish = () => 'null'

    const serializeArray = (array, depth) => {
    if (array.length === 0) {
    return '[]'
    }
    const pre = indent.repeat(depth)
    return (
    (depth > 0 ? '\n' : '') +
    array.map(entry => pre + '- ' + serialize(entry, depth + 1)).join('\n')
    )
    }

    const serializeObject = (object, depth) => {
    const entries = Object.entries(object)
    if (entries.length === 0) {
    return '{}'
    }
    if (keysSorted) {
    entries.sort(byKey)
    }
    const pre = indent.repeat(depth)
    return (
    (depth > 0 ? '\n' : '') +
    entries
    .map(([name, value]) => pre + name + ': ' + serialize(value, depth + 1))
    .join('\n')
    )
    }

    const indentStrings = (strings, depth) => {
    const pre = indent.repeat(depth)
    return strings.map(str => pre + str).join('\n')
    }

    const chunkString = (input, max) => {
    const result = []
    for (let i = 0; i < input.length; i += max) {
    result.push(input.substr(i, max))
    }
    return result
    }

    const serializeString = (value, depth) => {
    if (value.includes('\n')) {
    return '|\n' + indentStrings(value.split('\n'), depth)
    }
    if (value.length > maxStringLength) {
    return '>\n' + indentStrings(chunkString(value, maxStringLength), depth)
    }
    if (stringRequiresQuotes(value)) {
    return JSON.stringify(value)
    }
    return serializeDefault(value)
    }

    const serializeDefault = value => String(value)

    const strategies = {
    null: serializeNullish,
    array: serializeArray,
    object: serializeObject,
    string: serializeString
    }

    const serialize = (value, depth = 0) => {
    const type =
    value == null ? 'null' : Array.isArray(value) ? 'array' : typeof value
    const strategy = strategies[type] || serializeDefault
    return strategy(value, depth)
    }

    console.log(
    serialize({
    nullish: null,
    alsoNullish: undefined,
    string: 'foo bar',
    object: {
    emptyObject: {},
    key: 'value',
    array: ['one', 2, 'three', true, ['x', 'y', 'z']],
    stringWithWhiteSpace: ' foo bar ',
    emptyString: '',
    numeric: '1234.56',
    numericWithWhitespace: ' 78 '
    },
    emptyArray: [],
    nonEmptyArray: [1, null, 2, undefined, 3],
    multiline: '<parent>\n <child/>\n</parent>',
    longString: '0123456789abcdefghijklmnopqrstuvwxyz/'.repeat(20)
    })
    )