npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

decodable-js

v2.0.5

Published

`decodable-js` is a TypeScript/JavaScript library inspired by Swift's Codable. It validates types in JSON data, strips unknown fields, and returns a clean object shaped exactly like your schema. Based on configuration, it can either throw on type mismatch

Readme

decodable-js

Overview

decodable-js is a TypeScript/JavaScript library inspired by Swift's Codable. It validates types in JSON data, strips unknown fields, and returns a clean object shaped exactly like your schema. Based on configuration, it can either throw on type mismatches or silently skip misaligned fields. It also supports optional fields and string↔number conversion.

The return type is fully inferred from the struct via the Decode<T> conditional type — no manual type annotations needed.

Installation

# npm
npm install decodable-js

# yarn
yarn add decodable-js

Usage

Basic — flat object

import { decodable, T } from 'decodable-js';

const struct = {
    age:     T.number,
    address: T.string,
    visible: T.boolean,
    salary:  T.number_$  // optional
}

const data = {
    age:     12,
    address: '123 Maple Street',
    visible: true,
    salary:  5000,
    secret:  'will be stripped'
}

const result = decodable({ data, struct })
// {
//   age: 12,
//   address: '123 Maple Street',
//   visible: true,
//   salary: 5000
// }

Nested objects

Struct mirrors the shape of your data at any depth — unknown fields are stripped at every level:

const struct = {
    user: {
        name: T.string,
        address: {
            city:    T.string,
            country: T.string,
        }
    }
}

const data = {
    user: {
        name: 'Emily Johnson',
        password: 'hidden',       // stripped
        address: {
            city:    'Austin',
            country: 'USA',
            zip:     '78701',     // stripped
        }
    }
}

const result = decodable({ data, struct })
// { user: { name: 'Emily Johnson', address: { city: 'Austin', country: 'USA' } } }

Arrays of primitives

Wrap the element type in an array:

const struct = { tags: [T.string], ids: [T.number] }
const data   = { tags: ['js', 'ts'], ids: [1, 2, 3] }

decodable({ data, struct })
// { tags: ['js', 'ts'], ids: [1, 2, 3] }

Arrays of objects

const struct = {
    users: [{ id: T.number, name: T.string }]
}

const data = {
    users: [
        { id: 1, name: 'Emily Johnson', secret: 'stripped' },
        { id: 2, name: 'Michael Smith', secret: 'stripped' },
    ]
}

decodable({ data, struct })
// { users: [{ id: 1, name: 'Emily Johnson' }, { id: 2, name: 'Michael Smith' }] }

Top-level array

Pass arrays as both data and struct:

const data   = [{ id: 1, name: 'Emily Johnson' }, { id: 2, name: 'Michael Smith' }]
const struct = [{ id: T.number, name: T.string }]

decodable({ data, struct })
// [{ id: 1, name: 'Emily Johnson' }, { id: 2, name: 'Michael Smith' }]

Deep nesting (object → array of objects → array of objects)

Nesting is recursive — any combination of objects and arrays works:

const struct = {
    company: {
        name: T.string,
        departments: [{
            title: T.string,
            employees: [{
                id:   T.number,
                tags: [{ label: T.string }]
            }]
        }]
    }
}

decodable({ data, struct })  // strips extra fields at every level

enableConvert — string ↔ number conversion

const struct = { numbers: [T.string] }
const data   = { numbers: [1, '2', '3'] }  // 1 will be converted

decodable({ data, struct, enableConvert: true })
// { numbers: ['1', '2', '3'] }

If a string cannot be converted to a number (e.g. 'abc'), an error is thrown. Use silentMode: true to skip such values instead.

silentMode — skip mismatches instead of throwing

const struct = { id: T.number, name: T.string }
const data   = { id: 'wrong', name: 'Emily Johnson' }

decodable({ data, struct, silentMode: true })
// { name: 'Emily Johnson' }  — 'id' skipped, no error thrown

onDrop — observe skipped fields

onDrop is called whenever a field is silently skipped in silentMode. Receives the dot-path and the offending value:

const dropped: string[] = []

decodable({
    data:       { id: 'wrong', name: 'Emily Johnson' },
    struct:     { id: T.number, name: T.string },
    silentMode: true,
    onDrop:     (path, value) => dropped.push(`${path}: ${value}`)
})

// dropped → ['id: wrong']

API Reference

decodable(options)

| Option | Type | Default | Description | |---|---|---|---| | data | object \| array | — | JSON data to decode | | struct | object \| array | — | Schema defining expected types | | enableConvert | boolean | false | Convert between strings and numbers when possible | | silentMode | boolean | false | Skip invalid fields instead of throwing | | onDrop | (path: string, value: unknown) => void | — | Called for each silently skipped field |

Type markers (T)

| Marker | Type | Description | |---|---|---| | T.number | number | Any number | | T.string | string | Any string | | T.boolean | boolean | true or false | | T.null | null | Exactly null | | T.object | Record<string, any> | Any object — contents are passed through without validation | | T.array | any[] | Any array — contents are passed through without validation |

Optional types

Append _$ to mark a field as optional (type | undefined). If the field is absent, it is omitted from the result. If present, its type must match.

| Marker | Equivalent | |---|---| | T.number_$ | number \| undefined | | T.string_$ | string \| undefined | | T.boolean_$ | boolean \| undefined | | T.null_$ | null \| undefined | | T.object_$ | Record<string, any> \| undefined | | T.array_$ | any[] \| undefined |

You can also create an optional wrapper for any custom type with T.optional():

T.optional(T.number)   // same as T.number_$
T.optional({ id: T.number, name: T.string })  // optional nested object

Error messages

Errors include the full field path so the problem is easy to locate:

"user.address.city" is type "number" but expected "string" (got: 42)
"users[1].id" is type "string" but expected "number" (got: "abc")
"profile" key not found

Author

Alex Shumihin — initial work and maintenance.

For feedback or issues, please open a GitHub issue or submit a pull request.