Form compare
form-compare · version 1.0.0 · Forms · free, no key needed
Compare two filled form snapshots and return compact field and answer changes.
Use when you need to: compare filled forms · form snapshot diff.
Supported
- compare filled forms
- form snapshot diff
Not supported
- fuzzy matching
- html extraction
- pdf or ocr
- silent answer merge
Behavior
- Each snapshot carries its own fields and answers. Fields are diffed by exact id.
- Answers use the shared precedence only when both fields exist, the types are equal, and choice or multi sets contain the same strings.
- If the type or choice set differs, a non-missing answer is incomparable, even when a side is invalid for its own type. Choice order alone does not.
- A one-sided field adds or removes an answer only for null or a valid value. An invalid value stays invalid and is not added or removed.
- Both nulls compare equal and are unchanged. That is comparison equality, not validation. multi order-only changes are not value changes.
- Top-level counts repeat the nested field and answer counts. Nothing is truncated; over-budget output is rejected.
Input
left(object, required)right(object, required)
Output
fields(object, required)answers(object, required)counts(object, required)
Limits
- max input bytes: 262144
- max output bytes: 262144
- max input depth: 16
- max input nodes: 20000
- max fields: 100
- max choices: 50
- max respondents: 100
- max id bytes: 128
- max label bytes: 256
- max choice bytes: 128
- max answer string bytes: 4096
- max answer depth: 6
- max answer nodes: 50
- max changes: 500
- max mapping: 100
Example
Request input:
{
"left": {
"fields": [
{
"id": "email",
"label": "Email",
"type": "string",
"required": true
},
{
"id": "name",
"label": "Name",
"type": "string",
"required": true
}
],
"answers": {
"email": "a@b.test",
"name": "Ada"
}
},
"right": {
"fields": [
{
"id": "name",
"label": "Full name",
"type": "string",
"required": true
},
{
"id": "role",
"label": "Role",
"type": "choice",
"required": true,
"choices": [
"admin",
"member"
]
}
],
"answers": {
"name": "Ada Lovelace",
"role": "admin"
}
}
}
Response:
{
"result": {
"fields": {
"added": [
{
"id": "role",
"field": {
"id": "role",
"label": "Role",
"type": "choice",
"required": true,
"choices": [
"admin",
"member"
]
}
}
],
"removed": [
{
"id": "email",
"field": {
"id": "email",
"label": "Email",
"type": "string",
"required": true
}
}
],
"changed": [
{
"id": "name",
"changes": [
"label"
],
"left": {
"id": "name",
"label": "Name",
"type": "string",
"required": true
},
"right": {
"id": "name",
"label": "Full name",
"type": "string",
"required": true
}
}
],
"order_changed": false,
"counts": {
"added": 1,
"removed": 1,
"changed": 1,
"unchanged": 0,
"reordered": 0
}
},
"answers": {
"added": [
{
"id": "role",
"presence": "value",
"value": "admin"
}
],
"removed": [
{
"id": "email",
"presence": "value",
"value": "a@b.test"
}
],
"changed": [
{
"id": "name",
"reason": "value",
"left_presence": "value",
"right_presence": "value",
"left": "Ada",
"right": "Ada Lovelace"
}
],
"invalid": [],
"order_only": [],
"incomparable": [],
"unknown": [],
"counts": {
"added": 1,
"removed": 1,
"changed": 1,
"invalid": 0,
"order_only": 0,
"incomparable": 0,
"unknown": 0,
"unchanged": 0,
"fields": 3,
"left_missing": 1,
"left_null": 0,
"left_empty": 0,
"right_missing": 1,
"right_null": 0,
"right_empty": 0
}
},
"counts": {
"fields_added": 1,
"fields_removed": 1,
"fields_changed": 1,
"fields_unchanged": 0,
"fields_reordered": 0,
"answers_added": 1,
"answers_removed": 1,
"answers_changed": 1,
"answers_invalid": 0,
"answers_order_only": 0,
"answers_incomparable": 0,
"answers_unknown": 0,
"answers_unchanged": 0
}
}
}
How to call it
MCP
Connect https://computefirst.net/mcp (setup), then call execute with:
{
"id": "form-compare",
"version": "1.0.0",
"input": {
"left": {
"fields": [
{
"id": "email",
"label": "Email",
"type": "string",
"required": true
},
{
"id": "name",
"label": "Name",
"type": "string",
"required": true
}
],
"answers": {
"email": "a@b.test",
"name": "Ada"
}
},
"right": {
"fields": [
{
"id": "name",
"label": "Full name",
"type": "string",
"required": true
},
{
"id": "role",
"label": "Role",
"type": "choice",
"required": true,
"choices": [
"admin",
"member"
]
}
],
"answers": {
"name": "Ada Lovelace",
"role": "admin"
}
}
}
}
HTTP (no key)
curl -X POST https://computefirst.net/v1/tools/form-compare/versions/1.0.0/execute \
-H "Content-Type: application/json" \
-d '{"left":{"fields":[{"id":"email","label":"Email","type":"string","required":true},{"id":"name","label":"Name","type":"string","required":true}],"answers":{"email":"a@b.test","name":"Ada"}},"right":{"fields":[{"id":"name","label":"Full name","type":"string","required":true},{"id":"role","label":"Role","type":"choice","required":true,"choices":["admin","member"]}],"answers":{"name":"Ada Lovelace","role":"admin"}}}'
The machine-readable contract is at /v1/tools/form-compare/versions/1.0.0.
CLI
node cli.mjs run form-compare 1.0.0 --input input.json --base-url https://computefirst.net
Get the client at /clients/cli/.
Related tools
- Form answer diff: Diff two answer maps against one shared form definition.
- Form definition diff: Diff two form definitions by stable field id, including choice lists and field order.
- Form response diff: Reconcile two form response sets by exact respondent id.
- Form field map: Rename form fields with an explicit id map and reject collisions.
- Form response check: Report missing required answers, nulls, type failures, and choice failures.