What version of Ajv are you using? Does the issue happen if you use the latest version?
Latest
Test case
const ajv = require('ajv');
const v = new ajv({ removeAdditional: true, coerceTypes: true });
v.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
v.addSchema({
"$id": "test-schema.json",
"$schema": "http://json-schema.org/draft-06/schema#",
"definitions": {
"test::Test": {
"type": "object",
"properties": {
"id": {"type": "number"},
"name": {"oneOf": [{"type": "string"}, {"type": "null"}]},
"num": {"type": "number"}
},
"required": ["id"]
}
}
});
const t = v.compile({ '$ref': 'test-schema.json#/definitions/test::Test' });
const obj = { id: "1", name: null, num: "2" };
t(obj);
console.log(t.errors);
console.log(obj);
Validation result, data AFTER validation, error messages
[
{
keyword: 'oneOf',
dataPath: '.name',
schemaPath: 'test-schema.json#/definitions/test::Test/properties/name/oneOf',
params: { passingSchemas: [Array] },
message: 'should match exactly one schema in oneOf'
}
]
{ id: 1, name: null, num: '2' }
What results did you expect?
{ id: 1, name: null, num: 2 }
It passes validation if I change oneOf to anyOf, but the result is then
{ id: 1, name: '', num: 2 }
While I expect
{ id: 1, name: null, num: 2 }
Can you PLEASE make ajv never convert nulls to anything?..)
It's affected by the order of anyOf's variants.
Another similar issue is that {"anyOf": [{"type": "number"}, {"type": "string"}]} coerces "2" to 2, which is also incorrect.
Can you use “type”: [“null”, “string”]?
Having to maintain a specific order sucks :)
And anyway, the order doesn't fully fix the number|string case: number|string will convert strings to numbers, and string|number will convert numbers to strings. But both should probably just do nothing because both types are allowed...
number|string case will also convert "1.000000000000000001" to just 1, which is a precision loss. It's probably a small loss, but it's still a loss :)
Using a single type keyword with multiple types addresses the issue (and the order is not important in this case). For OpenAPI schemas that don’t allow multiple types there is “nullable” keyword they define.
Using oneOf to allow “null” and some other type is an anti-pattern, do not use it and use one of the option above.
Allowing the same property to be either a number or string can also be addressed with multiple
types but it is also an anti-pattern - a property should have one type allowed, possibly nullable.
The only legitimate use case for oneOf/anyOf keyword is discriminator pattern - the fact it is defined in a general Boolean logic way is a mistake in JSON Schema design that leads to multiple solutions to lens with tools and that causes OpenAPI to resort to workarounds and deviation from the JSON Schema spec.
Going to close the ticket - I don’t have any plans to change the coercing functionality - you just have to structure your schemas taking above into the account.
I have a lot of cases where I am accepting a hash with arbitrary "scalar" values which are string|number in case of json and I don't f*ing care if this is an antipattern or not... :-) my backend needs it, and js is a dynamic language after all. Also I'm sure that string containing a number must be indistinguishable from just the number for the backend. Other cases of coercion like arrays or string json decoding are unimportant or maybe even harmful, but string/number case is a must...
Ok, thanks for the discussion anyway, it seems I'll try to patch ajv myself, because current coerce functionality just breaks the data for me.
Can you use “type”: [“null”, “string”]?
Learning how to use the library and guided me into the right direction.
Most helpful comment
Can you use “type”: [“null”, “string”]?