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

boma

v2.1.1

Published

Simple JSON helper

Readme

Boma JSON helper

npm version

Super-simple helper for reading, saving and updating JSON files.

Works in synchronous mode by default. Pass async: true to use the asynchronous implementation.

Old synchronous calls remain unchanged.

Basic features

  • Using the createIfNotFound parameter in readJSON and addToJSONS, you can immediately create a missing file with the specified (default) object or array without any additional steps. This is convenient, for example, for writing configuration files.
  • addToJSON — reads a file and shallow merges objects or concatenates arrays.
  • Flag replaceNonSerializable — replaces function, undefined, bigint, loops, NaN, and Infinity with understandable markers.
  • Flag getSerializationIssues — shows all problematic fields with paths like [OBJECT].user.callback.
  • Single interface with async: true, instead of separate readFile / readFileSync.
  • You can get raw text using flag parseJSON: false.
  • By default, boma hides many errors when reading a file and returns null or {} — this is very convenient in a production environment. If necessary, you can set the throwError: true in readJSON and addToJSONS to catch and handle all errors yourself. However, write errors are much more critical, so boma always throws them.
  • Concurrent addToJSON calls within the same process are serialized per file, preventing lost updates from overlapping read–merge–write cycles. Important! This does not provide cross-process or cross-thread locking, so multiple workers or external writers may still cause race conditions.

Install

npm install boma

Import

import { readJSON, saveJSON, addToJSON } from 'boma';

Synchronous usage

Without the async option, all functions work synchronously.

const logs = readJSON({
  filePath: '/support.json',
  createIfNotFound: {},
  parseJSON: true,
  silent: true, // Do not log warnings
});

/*
logs = {
  any: {},
  key: {}
}
*/
saveJSON({
  filePath: '/test.json',
  objToSave: {
    any: {},
    key: {},
  },
  format: true,
  logSaving: false,
  silent: true,
});

/*
Will save:

{
  "any": {},
  "key": {}
}
*/
addToJSON({
  filePath: '/support.json',
  dataToAdd: {
    '6sdf89g7dghg': {
      any: 'data',
      someFunc: () => {},
    },
  },
  format: false,
  logSaving: false,
  replaceNonSerializable: true,
});

/*
Will save:

{
  "6sdf89g7dghg": {
    "any": "data",
    "someFunc": "function"
  }
}
*/

Asynchronous usage

Pass async: true to any main function.

In this mode:

  • readJSON returns a Promise with the read value;
  • saveJSON returns Promise<void>;
  • addToJSON returns Promise<void>.
const logs = await readJSON({
  filePath: '/support.json',
  createIfNotFound: {},
  parseJSON: true,
  silent: true,
  async: true,
});
await saveJSON({
  filePath: '/test.json',
  objToSave: {
    any: {},
    key: {},
  },
  format: true,
  logSaving: false,
  silent: true,
  async: true,
});
await addToJSON({
  filePath: '/support.json',
  dataToAdd: {
    newKey: {
      any: 'data',
    },
  },
  format: true,
  silent: true,
  async: true,
});

The same functions are used in both modes. Separate async imports are not required.

Reading JSON

const result = readJSON({
  filePath: '/test.json',
});

Default options:

{
  parseJSON: true,
  createIfNotFound: false,
  silent: true,
  async: false
}

When parseJSON is true, file content is parsed using JSON.parse.

When parseJSON is false, raw file content is returned as a string.

const rawContent = readJSON({
  filePath: '/test.json',
  parseJSON: false,
});

When createIfNotFound is true, a missing file is created with an empty object:

const result = readJSON({
  filePath: '/test.json',
  createIfNotFound: true,
});

You can also provide the initial object or array:

const result = readJSON({
  filePath: '/test.json',
  createIfNotFound: {
    created: true,
  },
});

Saving JSON

saveJSON({
  filePath: '/test.json',
  objToSave: {
    any: 'data',
  },
});

Use format: true to save formatted JSON with indentation and line breaks:

saveJSON({
  filePath: '/test.json',
  objToSave: {
    any: 'data',
  },
  format: true,
});

Use logSaving: true to log successful file saving when silent is false.

Non-serializable values

JSON does not support values such as:

  • undefined;
  • functions;
  • symbols;
  • bigint;
  • NaN and Infinity;
  • circular references.

Use replaceNonSerializable: true to replace such values with string type flags:

saveJSON({
  filePath: '/test.json',
  objToSave: {
    callback: () => {},
    missing: undefined,
    largeNumber: BigInt(10),
    invalidNumber: NaN,
  },
  replaceNonSerializable: true,
  format: true,
});

Will save:

{
  "callback": "function",
  "missing": "undefined",
  "largeNumber": "bigint",
  "invalidNumber": "non-finite-number"
}

Circular references are replaced with "circular".

Adding data to JSON

addToJSON reads the existing file, merges the new data and saves the result.

Objects are shallow merged:

// Existing file:
{
  "first": 1,
  "second": 2
}
addToJSON({
  filePath: '/test.json',
  dataToAdd: {
    second: 20,
    third: 3,
  },
});

Result:

{
  "first": 1,
  "second": 20,
  "third": 3
}

Existing object keys are overwritten by values from dataToAdd.

Arrays are concatenated:

// Existing file:
[1, 2]
addToJSON({
  filePath: '/test.json',
  dataToAdd: [3, 4],
});

Result:

[1, 2, 3, 4]

An object cannot be merged with an array. In this case, addToJSON throws:

Cannot merge array with object

A missing file is created automatically.

Typed reading

A result type can be passed to readJSON:

interface Config {
  port: number;
  production: boolean;
}

const config = readJSON<Config>({
  filePath: '/config.json',
});

Async mode uses the same generic:

const config = await readJSON<Config>({
  filePath: '/config.json',
  async: true,
});

Because parseJSON: false returns a string and reading errors can return null, the complete result type also includes string | null.

Types

Main types:

export type SerializablePrimitive = string | number | boolean | null;
export type SerializableArray = Serializable[];
export type SerializableObject = {
  [key: string]: Serializable;
};

export type Serializable =
  | SerializablePrimitive
  | SerializableArray
  | SerializableObject;

export interface ReadJSONProps {
  filePath: string;
  parseJSON?: boolean;
  createIfNotFound?:
    | boolean
    | SerializableObject
    | SerializableArray;
  silent?: boolean;
  async?: boolean;
}

export interface SaveJSONProps {
  filePath: string;
  objToSave: unknown;
  format?: boolean;
  logSaving?: boolean;
  replaceNonSerializable?: boolean;
  silent?: boolean;
  async?: boolean;
}

export interface addToJSONProps {
  filePath: string;
  dataToAdd:
    | Record<string, unknown>
    | unknown[];
  format?: boolean;
  logSaving?: boolean;
  replaceNonSerializable?: boolean;
  silent?: boolean;
  async?: boolean;
}

Types used for synchronous and asynchronous overloads are also exported:

export type ReadJSONSyncProps =
  ReadJSONProps & { async?: false };

export type ReadJSONAsyncProps =
  ReadJSONProps & { async: true };

export type SaveJSONSyncProps =
  SaveJSONProps & { async?: false };

export type SaveJSONAsyncProps =
  SaveJSONProps & { async: true };

export type AddToJSONSyncProps =
  addToJSONProps & { async?: false };

export type AddToJSONAsyncProps =
  addToJSONProps & { async: true };

export type ReadJSONResult<T = any> =
  T | string | null;

Helpers

The package also exports serialization and type-checking helpers:

isSerializable(value);
sanitizeNonSerializable(value);
getSerializationIssues(value);

isObjectLike(value);
isPlainMergeableObject(value);

isErrorWithCode(error);
isErrorWithMessage(error);
isSyntaxError(error);

getSerializationIssues returns all detected serialization problems with their object paths:

const issues = getSerializationIssues({
  user: {
    callback: () => {},
  },
});

/*
[
  {
    path: '[OBJECT].user.callback',
    kind: 'function',
    message:
      'Field "[OBJECT].user.callback" has non-serializable value of type "function"'
  }
]
*/