Skip to content

maxatwork/form2js

Repository files navigation

form2js

🚀 form2js is back — modernized and actively maintained.

Originally created in 2010, now rewritten for modern JavaScript, TypeScript, ESM, React, and modular usage.

Legacy version is available in the legacy branch.

Migrating from legacy form2js? Start with the migration guide.

Description

A small family of packages for turning form-shaped data into objects, and objects back into forms.

It is not a serializer, not an ORM, and not a new religion. It just does this one job, does it reliably, and leaves before anyone starts a committee about it.

Documentation

  • Docs Site - overview, installation, unified playground, and published API reference.
  • Migration Guide - map old form2js and jquery.toObject usage to the current package family.
  • API Reference Source - markdown source for the published API docs page.

Migration from Legacy

If you are moving from the archived single-package version, start with the migration guide.

Quick package map:

  • Legacy browser form2js(...) usage -> @form2js/dom
  • Legacy jQuery $("#form").toObject() usage -> @form2js/jquery
  • Server or pipeline FormData parsing -> @form2js/form-data
  • React submit handling -> @form2js/react
  • Object back into fields -> @form2js/js2form

The current project keeps the naming rules and core parsing model, but splits the old browser-era API into environment-specific packages.

Packages

Package npm Purpose Module Standalone Node.js
@form2js/react npm version React submit hook with parsing/validation state Yes No Browser-focused
@form2js/dom npm version Extract DOM fields to object (formToObject, form2js) Yes Yes With DOM shim (jsdom)
@form2js/form-data npm version Convert FormData/entries to object Yes No Yes
@form2js/js2form npm version Populate DOM fields from object (objectToForm, js2form) Yes No With DOM shim (jsdom)
@form2js/core npm version Path parsing and object transformation engine Yes No Yes
@form2js/jquery npm version jQuery plugin adapter ($.fn.toObject) Yes Yes Browser-focused

Installation

Install only what you need:

npm install @form2js/react react
npm install @form2js/dom
npm install @form2js/form-data
npm install @form2js/js2form
npm install @form2js/core
npm install @form2js/jquery jquery

For browser standalone usage, use script builds where available:

  • @form2js/dom: dist/standalone.global.js
  • @form2js/jquery: dist/standalone.global.js

Usage

@form2js/dom

HTML used in examples:

<form id="profileForm">
  <input name="person.name.first" value="Esme" />
  <input name="person.name.last" value="Weatherwax" />
  <label
    ><input type="checkbox" name="person.tags[]" value="witch" checked />
    witch</label
  >
</form>

Module:

import { formToObject } from "@form2js/dom";

const result = formToObject(document.getElementById("profileForm"));
// => { person: { name: { first: "Esme", last: "Weatherwax" }, tags: ["witch"] } }

Standalone:

<script src="https://unpkg.com/@form2js/dom/dist/standalone.global.js"></script>
<script>
  const result = formToObject(document.getElementById("profileForm"));
  // or form2js(...)
</script>

@form2js/form-data

Module (browser or Node 18+):

import { formDataToObject } from "@form2js/form-data";

const fd = new FormData(formElement);
const result = formDataToObject(fd);

Node.js note:

  • Node 18+ has global FormData.
  • You can also pass iterable entries directly, which is handy in server pipelines:
import { entriesToObject } from "@form2js/form-data";

const result = entriesToObject([
  ["person.name.first", "Sam"],
  ["person.roles[]", "captain"],
]);
// => { person: { name: { first: "Sam" }, roles: ["captain"] } }

With schema validation (works with Zod or any { parse(unknown) } schema):

import { z } from "zod";
import { formDataToObject } from "@form2js/form-data";

const PersonSchema = z.object({
  person: z.object({
    age: z.coerce.number().int().min(0)
  })
});

const result = formDataToObject([["person.age", "42"]], {
  schema: PersonSchema
});
// => { person: { age: 42 } }

Standalone:

  • Not shipped for this package. Use module imports.

@form2js/react

Module:

import { z } from "zod";
import { useForm2js } from "@form2js/react";

const FormDataSchema = z.object({
  person: z.object({
    name: z.object({
      first: z.string().min(1)
    })
  })
});

export function ProfileForm(): React.JSX.Element {
  const { onSubmit, isSubmitting, isError, error, isSuccess, reset } = useForm2js(
    async (data) => {
      // data is inferred from schema when schema is provided
      await sendFormData(data);
    },
    {
      schema: FormDataSchema
    }
  );

  return (
    <form
      onSubmit={(event) => {
        void onSubmit(event);
      }}
    >
      <input name="person.name.first" defaultValue="Sam" />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Saving..." : "Save"}
      </button>
      {isError ? <p>{String(error)}</p> : null}
      {isSuccess ? <p>Saved</p> : null}
      <button type="button" onClick={reset}>
        Reset state
      </button>
    </form>
  );
}

@form2js/jquery

HTML used in examples:

<form id="profileForm">
  <input name="person.name.first" value="Sam" />
  <input name="person.name.last" value="Vimes" />
</form>

Module:

import $ from "jquery";
import { installToObjectPlugin } from "@form2js/jquery";

installToObjectPlugin($);
const data = $("#profileForm").toObject({ mode: "first" });
// => { person: { name: { first: "Sam", last: "Vimes" } } }

Standalone:

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://unpkg.com/@form2js/jquery/dist/standalone.global.js"></script>
<script>
  const data = $("#profileForm").toObject({ mode: "combine" });
</script>

@form2js/js2form

HTML used in examples (before calling objectToForm):

<form id="profileForm">
  <input name="person.name.first" />
  <input name="person.name.last" />
</form>

Module:

import { objectToForm } from "@form2js/js2form";

objectToForm(document.getElementById("profileForm"), {
  person: { name: { first: "Tiffany", last: "Aching" } },
});
// fields are now populated in the form

Standalone:

  • Not shipped as a dedicated global bundle. Use module imports.

@form2js/core

Module:

import { entriesToObject, objectToEntries } from "@form2js/core";

const data = entriesToObject([
  { key: "person.name.first", value: "Vimes" },
  { key: "person.tags[]", value: "watch" },
]);

const pairs = objectToEntries(data);

Node.js:

  • Fully supported (no DOM dependency).

Standalone:

  • Not shipped for this package. Use module imports.

Legacy behavior notes

Compatibility with the old project is intentional.

  • Name paths define output shape (person.name.first).
  • Array and indexed syntax is preserved (items[], items[5].name).
  • Rails-style names are supported (rails[field][value]).
  • DOM extraction follows native browser form submission semantics for checkbox and radio values.
  • Unsafe key path segments (__proto__, prototype, constructor) are rejected by default.
  • This library does data shaping, not JSON/XML serialization.

Design boundaries and non-goals

These boundaries are intentional and are used for issue triage.

  • Sparse indexes are compacted in first-seen order (items[5], items[8] -> items[0], items[1]).
  • Type inference is minimal by design; DOM extraction keeps native string values instead of coercing checkbox/radio fields.
  • Unchecked indexed controls are omitted and therefore do not reserve compacted array slots; include another submitted field when row identity matters.
  • formToObject reads successful form control values, not option labels. Disabled controls (including disabled fieldset descendants) and button-like inputs are excluded unless you explicitly opt in to disabled values.
  • extractPairs/formToObject support nodeCallback; return SKIP_NODE to exclude a node entirely, or { name|key, value } to inject a custom entry.
  • Parser inputs reject unsafe path segments by default. Use allowUnsafePathSegments: true only with trusted inputs.
  • objectToForm supports nodeCallback; returning false skips the default assignment for that node.
  • objectToForm sets form control state and values; it does not dispatch synthetic change or input events.
  • Empty collections are not synthesized when no matching fields are present (for example, unchecked checkbox groups).
  • Dynamic key/value remapping (for example, converting key/val fields into arbitrary object keys) is application logic.
  • For file payloads and richer multipart semantics, use FormData and @form2js/form-data.

Contributing

Setup

npm ci

Local checks

npm run lint
npm run typecheck
npm run test
npm run build
npm run pack:dry-run

Local docs site

npm run docs
npm run docs:build

The homepage includes the unified playground for @form2js/react, @form2js/dom, @form2js/jquery, @form2js/js2form, @form2js/core, and @form2js/form-data.

GitHub Pages docs site

The published docs site is deployed by .github/workflows/pages.yml.

  • Trigger: pushes to master that touch apps/docs/**, docs/**, packages/**, .github/workflows/pages.yml, package.json, package-lock.json, turbo.json, or tsconfig.base.json, plus manual workflow_dispatch.
  • Output: apps/docs/dist.
  • URL: https://maxatwork.github.io/form2js/.

In repository settings, set Pages source to GitHub Actions once, and then the workflow handles updates.

Before opening a PR

  1. Keep changes focused to one problem area where possible.
  2. Add or update tests for behavior changes.
  3. Add a changeset (npm run changeset) for user-visible changes.
  4. Include migration notes in README if behavior or API changes.

Filing PRs and issues

Please include:

  • Clear expected vs actual behavior.
  • Minimal reproduction (HTML snippet or input entries).
  • Package name and version.
  • Environment (node -v, browser/version if relevant).

Release workflow

  • CI runs lint, typecheck, test, build, and package dry-run.
  • Releases are managed with Changesets and the published @form2js/* packages are versioned in lockstep.

Scope rewrite helper

Default scope is @form2js/*.

If you need to publish under another scope:

npm run scope:rewrite -- --scope @your-scope --dry-run
npm run scope:rewrite -- --scope @your-scope

This rewrites package names, internal dependencies, and import references.

License

MIT, see LICENSE.

About

Parse browser forms into structured JavaScript objects. Six adapters — React hooks, vanilla DOM, jQuery, FormData, and more. One coherent API.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors