fast-json-stringify icon indicating copy to clipboard operation
fast-json-stringify copied to clipboard

allOf Fails to Handle Array of if/then as Chained else Conditions

Open men232 opened this issue 9 months ago • 4 comments

Prerequisites

  • [x] I have written a descriptive issue title
  • [x] I have searched existing issues to ensure the bug has not already been reported

Fastify version

5.4.0

Plugin version

No response

Node.js version

22.14.0

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

15.5

Description

It appears that allOf cannot handle a sequence of if/then conditions as a chain of else clauses. This pattern would be extremely useful for scaling conditional validation logic, especially since libraries like AJV support it correctly.

When using allOf with an if/then block that includes array-based conditions, fast-json-stringify fails to serialize or apply logic as expected. AJV validates the same schema correctly.

When multiple if/then pairs are used inside allOf, only the first condition seems to be evaluated, and subsequent ones are ignored or misinterpreted.

Link to code that reproduces the bug

import fastJson from "fast-json-stringify";

const schema = {
  type: "object",
  properties: {
    kind: {
      enum: ["obj_1", "obj_2"],
    },
  },
  allOf: [
    {
      if: {
        properties: {
          kind: { type: "string", const: "obj_1" },
        },
      },
      then: {
        type: "object",
        properties: {
          kind: { type: "string" },
          prop_1: { type: "integer" },
        },
      },
    },
    {
      if: {
        properties: {
          kind: { type: "string", const: "obj_2" },
        },
      },
      then: {
        type: "object",
        properties: {
          kind: { type: "string" },
          prop_2: { type: "string" },
        },
      },
    },
  ],
  required: ["kind"],
};

doTest("obj 1", { kind: "obj_1", prop_1: 5 }, schema); // {"kind":"obj_1","prop_1":5} { ajv_valid: true }
doTest("obj 2", { kind: "obj_2", prop_2: "5" }, schema); // {"kind":"obj_2"} { ajv_valid: true }
doTest("obj 3", { kind: "obj_2", prop_2: 5 }, schema); // {"kind":"obj_2"} { ajv_valid: false }

function doTest(name, obj, schema) {
  const stringify = fastJson(schema);
  const validate = fastJson(obj, { mode: "debug" }).ajv.compile(schema);
  const ajv_valid = validate(obj);

  console.log(name, stringify(obj), { ajv_valid });
}

Expected Behavior

allOf should evaluate each if/then block in sequence, effectively allowing for a chain of mutually exclusive conditional branches (as a substitute for else if logic).

men232 avatar Jul 08 '25 17:07 men232

Dirty hot fix

function transformAllOf(schema) {
  handleAllOf(schema);

  for (const subSchema of Object.values(schema)) {
    if (!subSchema || typeof subSchema !== "object" || Array.isArray(subSchema)) {
      continue;
    }

    transformAllOf(subSchema);

    if (Array.isArray(subSchema.allOf)) {
      handleAllOf(subSchema);
    }
  }
}

function handleAllOf(schema) {
  if (!Array.isArray(schema.allOf) || !schema.allOf.length) {
    return;
  }

  let currentIf = schema.allOf.at(-1);

  for (let i = schema.allOf.length - 2; i >= 0; i--) {
    const currentSchema = schema.allOf[i];

    currentIf = {
      ...currentSchema,
      else: currentIf,
    };
  }

  delete schema.allOf;
  Object.assign(schema, currentIf);
}

men232 avatar Jul 08 '25 17:07 men232

Just curious, is anyOf not enough for you?

{
  "type": "object",
  "anyOf": [
    {
      "properties": {
        "kind": { "const": "obj_1" },
        "prop_1": { "type": "integer" }
      },
      "required": ["kind", "prop_1"]
    },
    {
      "properties": {
        "kind": { "const": "obj_2" },
        "prop_2": { "type": "string" }
      },
      "required": ["kind", "prop_2"]
    }
  ]
}

gurgunday avatar Jul 13 '25 11:07 gurgunday

I'm using a discriminator key to improve JSON Schema validation by checking that exactly one field has a constant value before validating an entire branch of the schema. In my case, anyOf performs slowly because I have around 10 different schema variants

men232 avatar Jul 13 '25 11:07 men232

Hi! 🌸 I tried reproducing this issue with your schema, and I confirm the same behavior — AJV validates correctly, but fast-json-stringify ignores chained conditions in allOf. I noticed your transformAllOf workaround, and I’d love to explore whether this can be integrated safely into fast-json-stringify without breaking other schemas. Do you think the right fix would be transforming allOf into chained if/then/else internally, or should this be aligned differently with AJV’s behavior? If it helps, I can start experimenting with a draft PR. ✨

riyap03 avatar Aug 20 '25 17:08 riyap03