Define the local evaluation task
A synthetic grader compares finite parsed JSON values. Object key order is ignored; array order, key presence, value types and null remain significant. No numeric-string coercion or removal of extra keys is permitted. Compare parsed structures recursively, rather than raw output text.
- Parse candidate before semantic comparison
- Ignore only object key presentation order
- Retain arrays, types and key presence
Unparsed, cyclic or non-JSON inputs need a separate parser/contract.
Use the filled cases
| Case | Supplied input | Expected structured output | Decision and reason |
|---|---|---|---|
| Object keys reordered | {"expected":{"a":1,"b":["X","Y"]},"answer":{"b":["X","Y"],"a":1}} | {"equal":true} | Only object presentation order changed. |
| Array reversed | {"expected":{"a":1,"b":["X","Y"]},"answer":{"a":1,"b":["Y","X"]}} | {"equal":false} | The local array sequence is meaningful and must not be sorted away. |
| Both comparison operands absent | {} | {"error":"comparison-operands"} | Two absent channels are not two equal finite JSON values. |
| Extra null-valued key | {"expected":{"a":1},"answer":{"a":1,"b":null}} | {"equal":false} | An extra key remains a structural difference even with null value. |
Work the extra-null-key case. Which output is required, and why?
Show the worked answer
{"equal":false} — An extra key remains a structural difference even with null value.
Run the self-contained evaluator
import assert from 'node:assert/strict';
function equal(a,b){
if(a===b)return true;if(a===null||b===null||typeof a!=='object'||typeof b!=='object')return false;
if(Array.isArray(a)||Array.isArray(b))return Array.isArray(a)&&Array.isArray(b)&&a.length===b.length&&a.every((v,i)=>equal(v,b[i]));
const ak=Object.keys(a).sort(),bk=Object.keys(b).sort();return equal(ak,bk)&&ak.every(k=>equal(a[k],b[k]));
}
function evaluate(x){if(!Object.hasOwn(x,'expected')||!Object.hasOwn(x,'answer')||x.expected===undefined||x.answer===undefined)return {error:'comparison-operands'};return {equal:equal(x.expected,x.answer)};}
const cases = [
{
"id": "keys-reordered",
"input": {
"expected": {
"a": 1,
"b": [
"X",
"Y"
]
},
"answer": {
"b": [
"X",
"Y"
],
"a": 1
}
},
"expected": {
"equal": true
}
},
{
"id": "array-reversed",
"input": {
"expected": {
"a": 1,
"b": [
"X",
"Y"
]
},
"answer": {
"a": 1,
"b": [
"Y",
"X"
]
}
},
"expected": {
"equal": false
}
},
{
"id": "both-operands-absent",
"input": {},
"expected": {
"error": "comparison-operands"
}
},
{
"id": "extra-null-key",
"input": {
"expected": {
"a": 1
},
"answer": {
"a": 1,
"b": null
}
},
"expected": {
"equal": false
}
}
];
for (const c of cases) assert.deepEqual(evaluate(c.input), c.expected, c.id);
console.log(JSON.stringify({checked:cases.length,caseIds:cases.map(c=>c.id)}));
Keep syntax validation separate from this equality criterion. Equal parsed structures can still contain a factually wrong value relative to the user’s source.