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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@fgv/ts-json

v4.0.2

Published

Typescript utilities for working with JSON

Downloads

52

Readme

Summary

Assorted JSON-related typescript utilities that I'm tired of copying from project to project.


Installation

With npm:

npm install ts-json

Overview

Type-Safe JSON

A handful of types express valid JSON as typescript types:

type JsonPrimitive = boolean | number | string | null;
interface JsonObject { [key: string]: JsonValue }
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
interface JsonArray extends Array<JsonValue> { }

Templated JSON

Templated JSON is type-safe JSON, with mustache template conversions applied to any string properties or keys using a supplied context.

    const src = {
        '{{prop}}': '{{value}}',
        literalValue: 'literal',
    };

    const result = JsonConverters.templatedJson({ prop: 'someProp', value: 'some value' }).convert(src);
    // result.value is {
    //    someProp: 'some value',
    //    litealValue: 'literal',
    // }

Multivalue Property Expansion

In a templated JSON object, a key of the form *name=value1,value2... or "[[name]]=value1,value2,... is expanded to multiple values, one per item in the comma-separated list that follows the equals sign. The individual values are generated by resolving the property value with a context that includes a variable with the name specified and each of the values in the comma separated list, in turn. For * expansion the result is a property named for the variable with an array of all values. For [[ expansion, the result is one property per value, where the property name matches the input value. For example:

    // with context:
    const context = {
        properties: ['first', 'second', 'third'],
    };

    // templated conversion of:
    const src = {
        '*prop={{properties}}': {
            '{{prop}}Prop': '{{prop}} value',
        },
        '[[prop]]={{properties}}': {
            '{{prop}}Prop': '{{prop}} value',
        },
    };

    // yields
    const expected = {
        first: {
            firstProp: 'first value',
        },
        second: {
            secondProp: 'second value',
        },
        third: {
            thirdProp: 'third value',
        },
        prop: ['first value', 'second value', 'third value'],
    };

The converter options for templated JSON allow for an override of the function that derives the context for each of the children, so it is possible to write a custom derivation function which sets different or additional values based on the value passed in.

Conditional JSON

Conditional JSON is templated JSON, but property names beginning with '?' reperesent conditional properties.

The value of any conditional property must be a JSON object. If the condition is satisfied, (a deep copy of) the children of the conditional property value are merged into the parent object. If the condition is not satisfied, the body is ignored.

Conditional Match Properties

Conditional match properties are identified by names with one of these forms:

    '?value1=value2'
    '?value1>=value2'
    '?value1<=value2'
    '?value1!=value2'
    '?value1>value2'
    '?value1<value2'

Where value1 and value2 are strings that do not include any of the valid operators. The condition is satisfied if value2 and value2 match according to the operator. For example:

    {
        '?someValue=someValue': {
            conditional1: 'conditional value 1',
        },
        '?someValue=someOtherValue': {
            conditional2: 'conditional value 2',
        },
        '?3>1': {
            conditional3: '3 is greater than 1',
        },
        '?3<1': {
            conditional4: 'this is wrong',
        },
        unconditional: true,
    }
    // yields
    {
        conditional1: 'conditional value 1',
        conditional3: '3 is greater than 1',
        unconditional: true,
    }

Defined Condition Properties

Defined condition properties are identified by names of the form:

    '?value'

Where value is any string, including the empty string. The condition is satisfied if value is not-empty or whitespace. For example:

    {
        '?someValue': {
            conditional: 'conditional value',
        },
        unconditional: 'unconditional value',
    }
    // yields
    {
        conditional: 'condtional value',
        unconditional: 'unconditional value',
    }

but

    {
        '?': {
            conditional: 'conditional value',
        },
        unconditional: 'unconditional value',
    }
    // yields
    {
        unconditional: 'unconditional value',
    }

Default Condition Properties

The special conditional property '?default' is satisfied if none of the immediately preceding conditional properties match, otherwise it is omitted. For example:

    {
        '?someValue=someOtherValue': {
            conditional1: 'conditional value 1',
        },
        '?default': {
            conditional1: 'default conditional value',
        }
    }
    // yields
    {
        conditional1: 'default conditional value',
    }

but

    {
        '?someValue=someValue': {
            conditional1: 'conditional value 1',
        },
        '?default': {
            conditional1: 'default conditional value',
        }
    }
    // yields
    {
        conditional1: 'conditional value 1',
    }

Flattened Unconditional Blocks

A default value is ignored if any conditional property in the same object was matched. To allow grouping of related conditional values with defaults, the conditional processor also supports unconditional properties with the '!' prefix. Any unconditional object properties are flattened and omitted. For example:

    {
        '!block1': {
            '?val1=val2': {
                gotVal1: 'match',
            },
            '?default': {
                gotVal1: 'default',
            }
        },
        '!block2': {
            '?val2=val3': {
                gotVal2: 'match',
            },
            '?default': {
                gotVal2: 'default',
            }
        },
    }
    // yields
    {
        gotVal1: 'default',
        gotVal2: 'default',
    }

Comments for Uniqueness

In any conditional property name, anything that follows the first '#' character is ignored. This makes it possible to include multiple conditions that match the same value. For example:

    {
        '?this=this': {
            conditional: 'conditional 1',
        },
        unconditional: 'unconditional',
        '?this=this': {
            conditional: 'conditional 2'
        }
    }

is not valid JSON, because two properties have the same name, but:

    {
        '?this=this#1': {
            conditional: 'conditional 1',
        },
        unconditional: 'unconditional',
        '?this=this#2': {
            conditional: 'conditional 2'
        }
    }
    // is valid, and yields:
    {
        unconditional: 'unconditional',
        conditional: 'conditional 2',
    }

Templating with Conditional JSON

Combined with mustache templating, this conditional syntax allows simple and powerful generation or consumption of conditional JSON files. For example, consider:

    {
        userName: '{{user}}',
        password: '{{pw}}',
        '?{{userType}}=admin': {
            rights: '...' // rights for admin
        },
        '?{{userType}}=bot': {
            rights: '...' // rights for bot
        }
        '?{{default}}': {
            rights: '...' // rights for normal user
        },
        '?{{externalId}}': {
            externalId: '{{externalId}}',
        }
    }

Given the context:

    {
        user: 'fred',
        pw: 'freds password',
        userType: 'admin',
        externalId: 'freds SSO credentials',
    }

Our example yields:

    {
        userName: 'fred',
        password: 'freds password',
        rights: '...', // rights for admin
        externalId: 'freds SSO credentials',
    }

But given the context:

    {
        user: 'r2d2',
        password: 'r2s pw',
        userType: 'bot',
    }

We get:

    {
        userName: 'r2d2',
        password: 'r2s pw',
        rights: '...', // rights for bot
    }

API

JsonEditor class

The JsonEditor can be used to edite JSON objects in place or to clone any JSON value, applying a default context and optional set of editor rules (e.g. for templating, conditional, multi-value or reference processing) to be applied.

mergeObjectInPlace method

The mergeObjectInPlace function takes a base object an object to be merged and updates the supplied base object with values from the merge object. For example:

    const base = {
        property1: 'value 1',
        property2: 'value 2',
    };
    const merge = {
        property2: 'value 2A',
        property3: 'value 3A',
    };
    const result = editor.mergeObjectInPlace(base, merge);
    // updates the base object and returns success with base object, which means
    // that both base and result.value have the shape:
    {
        property1: 'value 1',
        property2: 'value 2A',
        property3: 'value 3A',
    }

mergeObjectsInPlace method

The mergeObjectsInPlace function takes a base object and one or more objects to be merged, and updates the base object with values from each of the merge objects in the order supplied. for example:

    const base = {
        property1: 'value 1',
        property2: 'value 2',
    };
    const mergeA = {
        property2: 'value 2A',
        property3: 'value 3A',
    };
    const mergeB = {
        property3: 'value 3B',
        property4: 'value 4B',
    };
    const result = editor.mergeObjectsInPlace(base, mergeA, mergeB);
    // updates the base object and returns success with base object, which means
    // that both base and result.value have the shape:
    {
        property1: 'value 1',
        property2: 'value 2A',
        property3: 'value 3B',
        property4: 'value 4B',
    }

clone method

The clone method deep clones a supplied JSON value, applying all editor rules and a default or optionally supplied context.

Converters

A convenience set of ts-utils Converters and generators for the most common JSON conversions.

Simple JSON Converter

Use the json converter to convert unknown to type-safe JSON. Fails if the value to be converted is not valid JSON.

    import * as JsonConverters from '@fgv/ts-json/converters';

    const result = JsonConverters.json.convert(someUnknown);
    if (result.isSuccess()) {
        // someUnknown was valid JSON
        // jsonResult.value is a JsonValue deep copy of someUnknown
    }
    else {
        // someUnknown was not valid JSON
        // jsonResult.message describes the error
    }

Templated JSON Converter

Use the templatedJson converter to convert unknown to type-safe JSON, applying mustache template conversions to any string properties or keys using the supplied context.

    const src = {
        '{{prop}}': '{{value}}',
        literalValue: 'literal',
    };

    const result = JsonConverters.templatedJson({ prop: 'someProp', value: 'some value' }).convert(src);
    // result.value is {
    //    someProp: 'some value',
    //    litealValue: 'literal',
    // }

Conditional JSON Converter

Use the conditionalJson converter to convert unknown to type-safe JSON, applying mustache template conversions to any string properties or keys using the supplied context and merging or omitting conditional properties as appropriate. For example:

    const config =     {
        userName: '{{user}}',
        password: '{{pw}}',
        '?{{userType}}=admin': {
            rights: '...' // rights for admin
        },
        '?{{userType}}=bot': {
            rights: '...' // rights for bot
        }
        '?{{default}}': {
            rights: '...' // rights for normal user
        },
        '?{{externalId}}': {
            externalId: '{{externalId}}',
        }
    };
    const context =     {
        user: 'fred',
        pw: 'freds password',
        userType: 'admin',
        externalId: 'freds SSO credentials',
    };

    const result = JsonConverters.conditionalJson(context).convert(config);
    // succeeds and yields
    {
        userName: 'fred',
        password: 'freds password',
        rights: '...', // rights for admin
        externalId: 'freds SSO credentials',
    }