Snippets

Steven Berlan Nonstandard object serializer and deserializer

Created by Steven Berlan
// Am I happy with this? Not particulary. Vague challenge wanted
// a binary tree serializer/deserializer, but did not give any
// other details about if the nodes only would take scalar values
// or complex objects.
// So... general solution to encode/decode any object using a
// simple type prefix notation. Could be packed more if I allowed
// binary representation (create byte length variations for
// types, replace number type with binary types).


function serialize(obj) {
    if (obj === undefined) obj = 'u'
    else if (obj === null) obj = 'U'
    else if (typeof obj === 'boolean') obj = `b${+!!obj}`
    else if (typeof obj === 'string') obj = `s${obj.length}:${obj}`
    else if (obj instanceof Array) obj = `a${obj.length}${obj.map(i => serialize(i)).join('')}`
    else if (typeof obj === 'number') {
        if (isNaN(obj)) obj = 'N'
        else if (obj === -Infinity) obj = 'i'
        else if (obj === Infinity) obj = 'I'
        else obj = `n${obj}`
    } else if (typeof obj === 'function') {
        try {
            // Throws on bound or native function, make undefined in that case
            obj = obj.toString()
            Function(`(${obj})`)()
            obj = `f${obj.length}:${obj}`
        } catch (e) { obj = 'u' }
    } else if (typeof obj === 'object') {
        let collected = [undefined]
        let count = 0
        for (let key in obj) {
            collected.push(`s${key.length}:${key}${serialize(obj[key])}`)
            ++count
        }

        collected[0] = `o${count}`
        obj = collected.join('')
    }

    return obj
}

function deserialize(str) {
    let index = 0
    return str.length ? next() : undefined

    function expect(char) {
        if (str[index++] !== char) throw new Error(`Missing ${char} separator at ${index - 1}`)
    }

    function next() {
        switch (str[index++]) {
            case 'a': return nextArray()
            case 'b': return str[index++] === 1
            case 'f': return Function(`return (${nextString()})`)()
            case 'i': return -Infinity
            case 'I': return Infinity
            case 'n': return nextNumber()
            case 'N': return NaN
            case 'o': return nextObject()
            case 's': return nextString()
            case 'u': return undefined
            case 'U': return null
            default: throw new Error(`Bad token at ${index-1}: ${str[index-1]}`)
        }
    }

     function nextArray() {
        let len = nextNumber()|0, ret = []
        if (len < 0) throw new Error(`Bad array length at ${index - 1}: ${len}`)
        while (len--) ret.push(next())
        return ret
    }

    function nextNumber() {
        let num = str.slice(index).match(/-?(\d+(\.\d*)?|\.\d+)/)
        if (!num) throw new Error(`Number expected at ${index - 1}`)
        index += num[0].length
        return +num[0]
    }

    function nextObject() {
        let len = nextNumber()|0, ret = {}
        if (len < 0) throw new Error(`Bad object at ${index - 1}: ${len}`)
        while (len--) {
            expect('s')
            ret[nextString()] = next()
        }
        return ret
    }

    function nextString() {
        let len = nextNumber()
        expect(':')
        index += len
        return str.substr(index - len, len)
    }
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.