-
-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for ES6 Set #545
Conversation
Please make sure that validation works with Map, Set. By validation I mean cases when we've got |
Thanks! Let me add unit tests for them tmr! @ivan-tymoshenko |
It wouldn't be just test cases. You will have to make Ajv works with Map and Set as an 'object' type. If you have questions ask me I will tell you where to loot at. But it's not so easy to add support of two new types. I'm not sure if it worth it. |
🤔 I see... we have to update |
You need to make this test works. test('should stringify Set as an array', (t) => {
t.plan(2)
const schema = {
anyOf: [
{ type: 'array', items: { type: 'string' } },
{ type: 'object', properties: { foo: { type: 'string' } } }
]
}
const stringify = build(schema)
t.equal(stringify(new Set(['foo', 'bar'])), '["foo","bar"]')
t.equal(stringify({ foo: 'bar' }), '{"foo":"bar"}')
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
test/const.test.js
Outdated
|
||
const stringify = build(schema) | ||
const output = stringify({ | ||
constantSet: new Set([2, 3]), nonConstantSet: new Set(['Hi', 'it\'s', 'Tommy']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constantSet: new Set([2, 3]), nonConstantSet: new Set(['Hi', 'it\'s', 'Tommy']) | |
constantSet: new Set([4, 5, 6]), | |
nonConstantSet: new Set(['Hi', "it's", 'Tommy aka "Tom"']) |
test/const.test.js
Outdated
}) | ||
|
||
t.same( | ||
"{\"constantSet\":[2,3],\"nonConstantSet\":[\"Hi\",\"it's\",\"Tommy\"]}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"{\"constantSet\":[2,3],\"nonConstantSet\":[\"Hi\",\"it's\",\"Tommy\"]}", | |
"{\"constantSet\":[2,3],\"nonConstantSet\":[\"Hi\",\"it's\",\"Tommy aka \\"Tom\\"\"]}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validation should work with new types. I described above.
I'm working on the validation and have a new idea. Maybe it's easier to change the type in the schema from const schema = {
title: 'a set of booleans',
type: 'array',
items: { type: 'boolean' }
},
const data = new Set([false, true]), So we can internally convert the type in the validator to Of course we can also have the conversion if we keep the type as what we are doing in this PR: the type is simply |
@ivan-tymoshenko @mcollina @Eomm Wanna get you guys thoughts on using |
Going to implement this with |
I haven’t checked it yet. I will have some time tomorrow. We can discuss it. |
If i understand this correctly you want to implement a new type. Something like
instead of
Tbh i dont agree. type: "set" is not a valid json schema 7 type. The whole scope of this ES6 support is to implicitly serialize an ES6 Set as an Array. Keep in mind there is no way to deserialize a jsonstring to a Set. So there is information loss. But for that cases you would define a reviver for JSON.parse, which would revive the Set from the Array. So again: I totally disagree with adding a non-standard json-schema type. |
Yes, I'm with you. This would break everything:
It would not add any value to the user |
…to add-support-for-set
Thanks for reminding me of json schema 7 type! I take a look at json schema 7 type and noticed there's an uniqueItems keyword. I don't think we are using the keyword: const stringify = build({ type: 'array', uniqueItems: true })
console.log(stringify([1, 1, 2])) // "[1,1,2]" Would using |
What is the purpose to "indicate" a Set when uniqueItems is set to true? What is there to indicate? |
the I thought the issue here was: When a user wants to serialize a So, I don't get the point where the user must declare that it wants to support the Am I wrong? |
…to add-support-for-set
Thanks for @Uzlopak and @Eomm's input! The reason that I want the user to indicate they are stringifying a set is simple. I was struggling with the set validation and the indication helps with the implementation 😿. But luckily I realised we can just follow how we are dealing with There's one limitation tho. The if-else-then doesn't work on t.test('if/else with set', (t) => {
t.plan(2)
const schema = {
type: 'array',
if: { type: 'array', maxItems: 1 },
then: { items: { type: 'string' } },
else: { items: { type: 'number' } }
}
const stringify = build(schema)
t.equal(stringify(new Set(['1'])), JSON.stringify(['1']))
t.equal(stringify(new Set(['1', '2'])), JSON.stringify([1, 2])) // fail and stringified to '["1","2"]'
}) I am not sure the simple way to solve this... 🥵 Shall we simply write down the limitation? |
@ivan-tymoshenko |
You need one more check diff --git a/index.js b/index.js
index 8939e92..275e9ab 100644
--- a/index.js
+++ b/index.js
@@ -475,6 +475,9 @@ function addIfThenElse (location, input) {
elseLocation.schema = merge(schema, elseSchema)
return `
+ if (${input} instanceof Set) {
+ ${input} = Array.from(${input})
+ }
if (validator.validate("${ifSchemaRef}", ${input})) {
${buildValue(thenLocation, input)}
} else {
|
lib/validator.js
Outdated
type: 'object', | ||
errors: false, | ||
validate: (type, data) => { | ||
return data instanceof Set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't check the whole PR yet. But I think it's not enough here. You have to check that all restrictions like items
, minItems
, maxItems
will work in this case.
schema.type.pop('object') | ||
} | ||
|
||
return this.ajv.validate(schema, data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will work if this schema is a Set, but it won't work for any nested schemas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have an example of the nested schema?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const schema = {
anyOf: [
{ type: 'string' }
{
type: 'object',
properties: {
a: { type: 'array', items: { type: 'number' } }
}
}
]
}
const data1 = 'foo'
const data2 = { a: new Set([1, 2, 3]) }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/validator.js
Outdated
// undo what we do in convertSchemaToAjvFormat | ||
// to prevent infinite recursive call | ||
delete schema.isSet | ||
schema.type.pop('object') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
array's pop method always returns the last item and doesn't have any arguments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 7d169ea! But also found that the JSON.stringify
inside the error message doesn't handle Set
well. Can we please have a separate PR for that?
But I found a potential bug if I understand if-then-else logic correctly. test('anyOf with string and array', (t) => {
t.plan(2)
const schema = {
anyOf: [
{ type: 'string' },
{
type: 'object',
properties: {
a: {
type: 'array',
if: { items: { type: 'number' }, minItems: 1 },
then: { items: { type: 'number' } },
else: { const: ['const item'] }
}
}
}
]
}
const stringify = build(schema)
t.equal(stringify('foo'), '"foo"') // ok
t.equal(stringify({ a: [1, 2, 3] }), JSON.stringify({ a: [1, 2, 3] })) // ok
t.equal(
stringify({ a: [] }),
JSON.stringify({ a: ['const item'] })
) // throw error: The value {"a":[]} does not match schema definition.
}) |
schema.type = schema.type.filter(type => type !== 'object') | ||
} | ||
|
||
return this.ajv.validate(schema, data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that it will work with local references inside the schema
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const schema = {
anyOf: [
{ type: 'string' }
{
type: 'object',
properties: {
a: { type: 'array', items: { $ref: '#/anyOf/0' } }
}
}
]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah It doesn't work :(. The schema
arg in the validate function would just be { type: [ 'array' ], items: { '$ref': '#/anyOf/0' } }
.
Ugh let me think again. Please also let me know if you got any ideas.
Shouldnt we just convert the Set to an Array and then the normal array validation takes over? I have the feeling the current implementation is overcomplicated and does not what it wants to achieve. E.g. why do i need a keyword isSet (despite it should be named fjs_set or so) if I convert everything to an array anyway. Ajv can not iterate over the Set anyway. Or what do you think? |
Do you mean turning all the set in the object passed in? That’s right… this should be a way simpler solution. Would it have performance issue? We are traversing the entire object. I can try that. |
That is needed because we use The other statements are needed to manage the code branches we generate through the building phase of the serialization function.
The solution is not generic, but it is complete and simple. Unfortunately, we have many use cases ( I'm OK with this approach. |
I didn't get this point. At the beginning of this issue, I said that adding a new unsupported type wouldn't be easy. I'm not ok with adding only the serialization part without validation. |
If you want to make a simple solution, it will work. Of course, it will have some performance drop. Plus, remember that you shouldn't modify the original object. This means you will have to clone some part of it, which also takes some time. |
If you want to iterate over the Set etc.. I am also fine. Then implementing it needs to be a full solution and not something which works only partially. |
I guess what we are still missing in this PR is just Set validation with local reference. We have had all other features. Shall we try to get the validation with local reference first? It seems to me turning all But feel free to let me know what your thoughts are. Even if you think the whole PR is unnecessary. |
Instead of replacing the value, we could add a symbol with the Array to access later |
You still will have to make some checks during serialization/validation. If I understand your idea correctly, it won't help. |
I encountered some problems about cloning the object prototype methods when I tried to implement the convert-set-to-array solution. This seems to be a limitation/feature of our clone library (See davidmarkclements/rfdc#29). I didn't expect this issue would be this complicated. Would like to take a break from it first. Feel free to tackle this when you are interested! 😸 Really appreciate the help from Ivan, Uzlopak & Manuel. |
What this PR does
An issue from #301.
We add support for
Set
in this PR only. Anew Set([1, 2, 3, 4])
will be stringified to[1,2,3,4]
Unit tests:
maxItems
,contains
,minContains
PR for
Map
Working on
Map
now. Instead of having map and set feature in one PR, we have a separate PR forMap
later?Checklist
npm run test
andnpm run benchmark
and the Code of conduct