https://github.com/maxatwork/form2js
Parse browser forms into structured JavaScript objects. Six adapters — React hooks, vanilla DOM, jQuery, FormData, and more. One coherent API.
https://github.com/maxatwork/form2js
deserialization form form-data forms hook html javascript jquery jquery-plugin json object-transform parser react serialization server submit
Last synced: 17 days ago
JSON representation
Parse browser forms into structured JavaScript objects. Six adapters — React hooks, vanilla DOM, jQuery, FormData, and more. One coherent API.
- Host: GitHub
- URL: https://github.com/maxatwork/form2js
- Owner: maxatwork
- License: mit
- Created: 2010-09-13T09:49:47.000Z (over 15 years ago)
- Default Branch: master
- Last Pushed: 2026-03-27T23:02:13.000Z (22 days ago)
- Last Synced: 2026-03-28T05:43:58.504Z (21 days ago)
- Topics: deserialization, form, form-data, forms, hook, html, javascript, jquery, jquery-plugin, json, object-transform, parser, react, serialization, server, submit
- Language: TypeScript
- Homepage: https://maxatwork.github.io/form2js/
- Size: 638 KB
- Stars: 632
- Watchers: 34
- Forks: 135
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: changeset/config.json
- License: LICENSE
Awesome Lists containing this project
README
# 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](https://github.com/maxatwork/form2js/tree/legacy).
Migrating from legacy form2js? Start with the [migration guide](https://maxatwork.github.io/form2js/migrate/).
## 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](https://maxatwork.github.io/form2js/) - overview, installation, unified playground, and published API reference.
- [Migration Guide](https://maxatwork.github.io/form2js/migrate/) - map old `form2js` and `jquery.toObject` usage to the current package family.
- [API Reference Source](docs/api-index.md) - 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](https://maxatwork.github.io/form2js/migrate/).
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`](https://www.npmjs.com/package/@form2js/react) | [](https://www.npmjs.com/package/@form2js/react) | React submit hook with parsing/validation state | Yes | No | Browser-focused |
| [`@form2js/dom`](https://www.npmjs.com/package/@form2js/dom) | [](https://www.npmjs.com/package/@form2js/dom) | Extract DOM fields to object (`formToObject`, `form2js`) | Yes | Yes | With DOM shim (`jsdom`) |
| [`@form2js/form-data`](https://www.npmjs.com/package/@form2js/form-data) | [](https://www.npmjs.com/package/@form2js/form-data) | Convert `FormData`/entries to object | Yes | No | Yes |
| [`@form2js/js2form`](https://www.npmjs.com/package/@form2js/js2form) | [](https://www.npmjs.com/package/@form2js/js2form) | Populate DOM fields from object (`objectToForm`, `js2form`) | Yes | No | With DOM shim (`jsdom`) |
| [`@form2js/core`](https://www.npmjs.com/package/@form2js/core) | [](https://www.npmjs.com/package/@form2js/core) | Path parsing and object transformation engine | Yes | No | Yes |
| [`@form2js/jquery`](https://www.npmjs.com/package/@form2js/jquery) | [](https://www.npmjs.com/package/@form2js/jquery) | jQuery plugin adapter (`$.fn.toObject`) | Yes | Yes | Browser-focused |
## Installation
Install only what you need:
```bash
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:
```html
witch
```
Module:
```ts
import { formToObject } from "@form2js/dom";
const result = formToObject(document.getElementById("profileForm"));
// => { person: { name: { first: "Esme", last: "Weatherwax" }, tags: ["witch"] } }
```
Standalone:
```html
const result = formToObject(document.getElementById("profileForm"));
// or form2js(...)
```
### `@form2js/form-data`
Module (browser or Node 18+):
```ts
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:
```ts
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):
```ts
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:
```ts
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 (
{
void onSubmit(event);
}}
>
{isSubmitting ? "Saving..." : "Save"}
{isError ?
{String(error)}
: null}
{isSuccess ? Saved
: null}
Reset state
);
}
```
### `@form2js/jquery`
HTML used in examples:
```html
```
Module:
```ts
import $ from "jquery";
import { installToObjectPlugin } from "@form2js/jquery";
installToObjectPlugin($);
const data = $("#profileForm").toObject({ mode: "first" });
// => { person: { name: { first: "Sam", last: "Vimes" } } }
```
Standalone:
```html
const data = $("#profileForm").toObject({ mode: "combine" });
```
### `@form2js/js2form`
HTML used in examples (before calling `objectToForm`):
```html
```
Module:
```ts
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:
```ts
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
```bash
npm ci
```
### Local checks
```bash
npm run lint
npm run typecheck
npm run test
npm run build
npm run pack:dry-run
```
### Local docs site
```bash
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:
```bash
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`.