Repository: json-schema-faker/json-schema-faker
Pull request #847 (2025-06-25 06:07:19)

fix:Fix the issue of not generating irrelevant data
For the existing scenarios of not and enum, I think this is inappropriate. When using the notValue() function to generate data that does not conform to the schema, although constraints in the schema (such as enum, minimum, maximum, etc.) need to be avoided, it is still necessary to ensure that the final generated data type (type) conforms to the specified type.



```javascript
else if (schema.enum) {
    let value;
    do {
      value = anyValue();
    } while (schema.enum.indexOf(value) !== -1);
    copy.enum = [value];
  }


function anyValue() {
  return random.pick([
    false,
    true,
    null,
    -1,
    NaN,
    Math.PI,
    Infinity,
    undefined,
    [],
    {},
    // FIXME: use built-in random?
    Math.random(),
    Math.random().toString(36).substr(2),
  ]);
}
```



`


Patch:
diff --git a/package.json b/package.json
index be7c2dc..5968df6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "json-schema-faker",
-  "version": "0.5.8",
+  "version": "0.5.9",
   "description": "JSON-Schema + fake data generators",
   "homepage": "http://json-schema-faker.js.org",
   "types": "index.d.ts",
diff --git a/src/lib/core/notValueUtils.mjs b/src/lib/core/notValueUtils.mjs
new file mode 100644
index 0000000..cde62d1
--- /dev/null
+++ b/src/lib/core/notValueUtils.mjs
@@ -0,0 +1,79 @@
+import utils from './utils.mjs';
+import env from './constants.mjs';
+import random from './random.mjs';
+
+import numberType from '../types/number.mjs';
+import integerType from '../types/integer.mjs';
+import booleanType from '../types/boolean.mjs';
+import stringType from '../types/string.mjs';
+
+const TYPE_GENERATOR = {
+  number: numberType,
+  integer: integerType,
+  string: stringType,
+  boolean: booleanType,
+};
+
+
+
+export function notValue(schema, parent) {
+  const copy = utils.merge({}, parent);
+
+  if (typeof schema.minimum !== 'undefined') {
+    copy.maximum = schema.minimum;
+    copy.exclusiveMaximum = true;
+  }
+
+  if (typeof schema.maximum !== 'undefined') {
+    copy.minimum = schema.maximum > copy.maximum ? 0 : schema.maximum;
+    copy.exclusiveMinimum = true;
+  }
+
+  if (typeof schema.minLength !== 'undefined') {
+    copy.maxLength = schema.minLength;
+  }
+
+  if (typeof schema.maxLength !== 'undefined') {
+    copy.minLength = schema.maxLength > copy.maxLength ? 0 : schema.maxLength;
+  }
+
+  if (schema.type) {
+    copy.type = random?.pick(
+      env.SCALAR_TYPES.filter(x => {
+        const types = Array.isArray(schema.type) ? schema.type : [schema.type];
+
+        return types.every(type => {
+          // treat both types as _similar enough_ to be skipped equal
+          if (x === 'number' || x === 'integer') {
+            return type !== 'number' && type !== 'integer';
+          }
+
+          return x !== type;
+        });
+      }),
+    );
+  } else if (schema.enum) {
+    let value;
+    const expectedType = copy.type;
+
+    do {
+      try {
+        value = TYPE_GENERATOR[expectedType]?.(utils.omitProps(copy, ['not'])) ?? utils.anyValue();
+      } catch (e) {
+        value = utils.anyValue();
+      }
+    } while (schema.enum.includes(value));
+
+    copy.enum = [value];
+  }
+
+  if (schema.required && copy.properties) {
+    schema.required.forEach(prop => {
+      delete copy.properties[prop];
+    });
+  }
+
+  // TODO: explore more scenarios
+
+  return copy;
+}
diff --git a/src/lib/core/traverse.mjs b/src/lib/core/traverse.mjs
index aa14a38..4b71c1e 100644
--- a/src/lib/core/traverse.mjs
+++ b/src/lib/core/traverse.mjs
@@ -4,6 +4,7 @@ import ParseError from './error.mjs';
 import inferType from './infer.mjs';
 import types from '../types/index.mjs';
 import optionAPI from '../api/option.mjs';
+import { notValue } from './notValueUtils.mjs';
 
 function getMeta({ $comment: comment, title, description }) {
   return Object.entries({ comment, title, description })
@@ -62,7 +63,7 @@ function traverse(schema, path, resolve, rootSchema) {
   }
 
   if (schema.not && typeof schema.not === 'object') {
-    schema = utils.notValue(schema.not, utils.omitProps(schema, ['not']));
+    schema = notValue(schema.not, utils.omitProps(schema, ['not']));
 
     // build new object value from not-schema!
     if (schema.type && schema.type === 'object') {
diff --git a/src/lib/core/utils.mjs b/src/lib/core/utils.mjs
index c79c266..f6f98b3 100644
--- a/src/lib/core/utils.mjs
+++ b/src/lib/core/utils.mjs
@@ -1,5 +1,4 @@
 import optionAPI from '../api/option.mjs';
-import env from './constants.mjs';
 import random from './random.mjs';
 
 const RE_NUMERIC = /^(0|[1-9][0-9]*)$/;
@@ -53,7 +52,7 @@ function isScalar(value) {
   return ['number', 'boolean'].includes(typeof value);
 }
 
- /**
+/**
  * Returns true/false whether the object parameter has its own properties defined
  *
  * @param obj
@@ -62,7 +61,7 @@ function isScalar(value) {
  */
 function hasProperties(obj, ...properties) {
   return properties.filter(key => {
-    return typeof obj[key] !== 'undefined';
+      return typeof obj[key] !== 'undefined';
   }).length > 0;
 }
 
@@ -359,60 +358,7 @@ function hasValue(schema, value) {
   if (schema.const) return schema.const === value;
 }
 
-function notValue(schema, parent) {
-  const copy = merge({}, parent);
-
-  if (typeof schema.minimum !== 'undefined') {
-    copy.maximum = schema.minimum;
-    copy.exclusiveMaximum = true;
-  }
-
-  if (typeof schema.maximum !== 'undefined') {
-    copy.minimum = schema.maximum > copy.maximum ? 0 : schema.maximum;
-    copy.exclusiveMinimum = true;
-  }
 
-  if (typeof schema.minLength !== 'undefined') {
-    copy.maxLength = schema.minLength;
-  }
-
-  if (typeof schema.maxLength !== 'undefined') {
-    copy.minLength = schema.maxLength > copy.maxLength ? 0 : schema.maxLength;
-  }
-
-  if (schema.type) {
-    copy.type = random.pick(env.SCALAR_TYPES.filter(x => {
-      const types = Array.isArray(schema.type) ? schema.type : [schema.type];
-
-      return types.every(type => {
-        // treat both types as _similar enough_ to be skipped equal
-        if (x === 'number' || x === 'integer') {
-          return type !== 'number' && type !== 'integer';
-        }
-
-        return x !== type;
-      });
-    }));
-  } else if (schema.enum) {
-    let value;
-
-    do {
-      value = anyValue();
-    } while (schema.enum.indexOf(value) !== -1);
-
-    copy.enum = [value];
-  }
-
-  if (schema.required && copy.properties) {
-    schema.required.forEach(prop => {
-      delete copy.properties[prop];
-    });
-  }
-
-  // TODO: explore more scenarios
-
-  return copy;
-}
 
 function validateValueForSchema(value, schema) {
   const schemaHasMin = schema.minimum !== undefined;
@@ -553,7 +499,6 @@ export default {
   clone,
   short,
   hasValue,
-  notValue,
   anyValue,
   validate,
   validateValueForSchema,


Test patch:
diff --git a/tests/schema/core/types/not.json b/tests/schema/core/types/not.json
index a49fe92..f44496a 100644
--- a/tests/schema/core/types/not.json
+++ b/tests/schema/core/types/not.json
@@ -124,6 +124,16 @@
           }
         },
         "valid": true
+      },
+      {
+        "description": "should generate an integer not in enum values",
+        "schema": {
+          "type": "integer",
+          "maximum": 5,
+          "minimum": 2,
+          "not": { "enum": [1, 2, 5, 4] }
+        },
+        "equal": 3
       }
     ]
   }