Skip to content
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

Ambiguous default keywords in nested objects #238

Closed
EternalDeiwos opened this issue Jan 30, 2017 · 9 comments
Closed

Ambiguous default keywords in nested objects #238

EternalDeiwos opened this issue Jan 30, 2017 · 9 comments

Comments

@EternalDeiwos
Copy link

EternalDeiwos commented Jan 30, 2017

Myself and @christiansmith have come across an interesting case where we are unsure of what behaviour should be followed when we encounter a schema like this:

let schema = {
  properties: {
    a: {
      type: 'object',
      default: { b: 100 },
      properties: {
        b: { type: 'integer', default: 20 },
        c: { type: 'string', default: 'foo' }
      }
    }
  }
}

Provided that this is a valid schema and is being validated against a blank object ({}), should the resultant object -- after defaults have been assigned but before validation -- look like this:

{ a: { b: 100 } }

or this:

{ a: { b: 20, c: 'foo' } }

@handrews
Copy link
Contributor

@EternalDeiwos validation does not inherently apply defaults. The choice of whether and when do to that lies with the application. That application can also decide whether and how to recursively apply defaults.

I'm not sure if there is a clear recommendation here. Default is intentionally somewhat under-specified (although there's a great deal of discussion around that, most notably #204 ). If you took the top-level default, but also looked for opportunities to further fill in defaults, you could end up {"a": {"b": 100, "c": "foo"}}. There is nothing special about the {"b": 100} instance that is used as a top-level default, so it's just as reasonable to apply the default for "c" to it as well.

I realize that's not a very satisfying answer. I think (as indicated by #204 and others) that we have some work to do clarifying the intent and usage of "default" before we reach RFC.

@EternalDeiwos
Copy link
Author

@handrews Thanks for the response, I'm posting our planned course of action here mainly for the record but also for further discussion, suggestions and commentary.

What we are trying to do is Object Initialization from a JSON Schema. Obviously this is not part of the JSON Schema specs but the behaviour we use is largely determined by the behaviour defined for JSON Schema Validation. So we spent a little time reasoning about this and related cases and came up with the following table (the cases not seen here are uninteresting for this topic):

Parent Source Present Parent Default Present Nested Source Present Nested Default Present Behaviour
1 1 0 1 Assign nested default
1 0 0 1 Assign nested default
0 1 0 1 ?
1 1 0 1 Assign nested default

? => the case in question

The cases other than the case in question seemed fairly intuitive and obvious to us and all point to prioritising the nested over the top-level default. With the lack of a clear recommendation, we decided to follow this trend meaning that -- after initialisation -- the above schema will result in an object like this:

{ a: { b: 20, c: 'foo' } }

@epoberezkin
Copy link
Member

epoberezkin commented Jan 31, 2017

Not sure what does "Source" mean. If anything, result by @handrews ({"a": {"b": 100, "c": "foo"}}) makes more sense to me because once "b" property is filled from the parent schema, the default in nested schema no longer applies (as the value is already present in data).

In general it's a murky territory and with a bit of luck you'll end up with a complex rollback logic to cater for anyOf etc. (see Themis). I'd rather we don't have it in the spec at all (as it is now), so it's purely informational, than having over-specified mechanism of applying defaults.

@EternalDeiwos
Copy link
Author

EternalDeiwos commented Jan 31, 2017

@epoberezkin source refers to the input object for validation (or initialisation in our case). This makes logical sense, however if we consider the resulting behaviour for the above schema with the following input/source objects:

Input Output
{} { a: { b: 100, c: 'foo' } }
{ a: {} } { a: { b: 20, c: 'foo' } }

Would this behaviour make the default keyword more or less predictable? We made a judgement call but I am still unsure as to which is better and why.

In general it's a murky territory and with a bit of luck you'll end up with a complex rollback logic to cater for anyOf etc. (see Themis). I'd rather we don't have it in the spec at all (as it is now), so it's purely informational, than having over-specified mechanism of applying defaults.

The purpose of a spec in the first place is standardisation. If the behaviour is not standardised then that negatively impacts the portability of JSON Schema between implementations. This is probably a topic for a different discussion but that strikes me as counterproductive.

@epoberezkin
Copy link
Member

epoberezkin commented Jan 31, 2017

¯\_(ツ)_/¯

@awwright
Copy link
Member

awwright commented Jan 31, 2017

@EternalDeiwos My own usage of "default" is for user interfaces and other applications where you've just created a new array item, or created a property in an object, and it needs a sensible initial value. Usually this default should be a no-op value (one that can be added or removed with no change in behavior), but this isn't necessarily true.

So for example, consider me creating a JSON Schema document. I add to it the "items" keyword, and so my editor will auto-complete:

{
"items": []
}

Which is sensible because this is the same thing as {}.


If I understand, you're using "default" to help fill in values that have been left undefined by a user. Perhaps you're parsing a config file, and you want "port" to "default" to 8080 unless modified by the JSON instance.

This is one of the first ways I used the keyword. This isn't an incompatible way to read it, but it can cause some problems. A property not existing can be just as distinct a value as a value that does exist.

It might be the case that

{}
{ port: null }
{ port: 0 }
{ port: false }
{ port: 8080 }

all exhibit different behaviors. Yet, there might still be a "default" (like 8080) even if the behavior is distinct from {} (which might mean "no opening a port")

@EternalDeiwos
Copy link
Author

While solving the ambiguity problem in our implementation we found that the solution proposed by @handrews and @epoberezkin did seem to be the most natural and simplest way of doing it. Thanks again to all those involved.

@dolmen
Copy link

dolmen commented Feb 9, 2017

As just a reader of the spec I have always interpreted the default property as documentation that says how an application will interpret the absence of the value. Not how some holes must be filled to build a "complete" value. This is an important difference because a missing property is not the same as a property having the same value as the default. This difference may have a meaning for the application.
So "assign" is irrelevant regarding default.

@handrews
Copy link
Contributor

What we are trying to do is Object Initialization from a JSON Schema.

Coming back to this, I think that this falls into the code generation and/or related vocabularies being considered in the https://github.com/json-schema-org/json-schema-vocabularies repository. If that seems of interest I encourage re-filing this there.

The general annotation vocabulary (a.k.a. the "metadata" section of the validation spec) intentionally avoids saying how the annotations MUST be used. The use-case-specific vocabularies would be the right place to define how to build on the annotations for a specific use case. In this example, depending on the use case you may want to prefer the nested or the parent default, but there's no universal right answer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants