-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Handle circular references #557
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
Conversation
Hi, this looks good, but I'd like to handle #554 before merging this -- I think it will make review easier. Also, I saw "Schemas SHOULD NOT make use of infinite recursive nesting like this, the behavior is undefined." in http://json-schema.org/latest/json-schema-core.html, does this mean we can punt on handling the infinite recursion case? If we can't work on Node, that seems like a pretty big limitation... |
@glasserc: I agree with handling #554 first—as you can see, I actually rebased to include that fix in here because they're dependent. Anyway, I fixed the Node issue in 6ebb3e4—per this comment on my bug report, the problem was that I hadn't defined As regards the spec, I did wonder about that... but am not 100% sure whether it's talking about the same thing? I can't see how circularities can be avoided in many real world scenarios e.g. where the schema for "person" has a "children" property that is an array of "person" objects. I ran into exactly this problem with the (draft) UBL JSON schema (albeit with Locations that have AlternativeLocation children). So, even if the JSON Schema spec currently discourages circularities I suspect that will eventually have to be changed? json-ref-schema-parser, which I mentioned previously, happily handles a variety of different types of circularity. |
@@ -45,7 +45,7 @@ class ObjectField extends Component { | |||
onBlur, | |||
} = this.props; | |||
const { definitions, fields, formContext } = this.props.registry; | |||
const { SchemaField, TitleField, DescriptionField } = fields; | |||
const { ObjectPropertyField, TitleField, DescriptionField } = fields; |
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.
With circular schema, we must avoid rendering everything: object properties should only be rendered when necessary. I define a new component, ObjectPropertyField
that only renders the underlying SchemaField
when necessary—i.e. if the schema requires the property to be present, if the formData
contains an explicit value for it, or if the user explicitly requests the property to be shown.
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.
Consider putting this comment as JSDoc on the ObjectPropertyField
class.
const isDefault = | ||
!(name in formData) || | ||
typeof Object.getOwnPropertyDescriptor(formData, name).get === | ||
"function"; |
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.
Here I'm trying determine whether the value in formData
was given explicitly, or if it's just a default value from the lazily-evaluated Proxy object (this is used in the ObjectPropertyField
component to determine whether the underlying SchemaField
should be rendered). It may be better to pass explicit formData
separately from default 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.
Yes, I have to say this is super ugly, and passing explicit formData
and defaults
would be better in a perfect world. On the other hand, I'm not sure I want to separate them as part of this PR. I think extracting this into a variable called isDefault
is good enough, although maybe you could add a FIXME comment explaining this.
</div> | ||
); | ||
} else { | ||
return <button onClick={this.show}>ADD {title}</button>; |
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.
No doubt these <button/>
elements should be replaced with a better UI?
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 pretty nervous about the UX in general. I know that @n1k0 took a swing at collapsibility before in #268 and wasn't able to find a UX that he found felt right for most use cases, and this feels like a similar situation to me. Can we add this only if we are in a circular-schema situation? (N.B. I haven't actually tried running the code so this may not be as intrusive as I imagine it being.)
…ence of a schema with [circular references](https://github.com/BigstickCarpet/json-schema-ref-parser/blob/master/docs/README.md#circular-refs). NB this approach uses ES2015 Proxies, which can't be polyfilled and therefore it requires native support from the JS engine. V8 currently has [a bug](https://bugs.chromium.org/p/v8/issues/detail?id=6290) which causes test suite to fail under Node. No tests written yet.
…hen explicitly necessary. Tests updated to make mandatory object properties that were previously optional but assumed to be mandatory.
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.
Hi, sorry for taking so long to get to this again. 😞
I agree with your analysis, that recursive schemas are likely to be important as JSON Schema becomes more utilized. I'm really grateful that you're working on it. I'm a little nervous it may turn out to be a bigger idea than can be contained in just one PR. In particular, I agree with your comment in #557 (comment) that trying to determine whether something is a default might be easier if we change how form data and defaults are given to components. If we do decide to make that change, that might be easier as a separate PR.
However, my biggest concern with this PR is the addition of expand/collapse buttons on everything. (Expand/collapse buttons, of course, are another idea that is big enough that it could be its own PR. See e.g. #268 and #271.) My interpretation of the history of the RJSF project is that we tend to follow the structure of the schema as much as possible, and that more generally, we try to take as simplistic an approach as possible, hoping that it yields a better UX. I'm hesitant to jeopardize the common case in the pursuit of the complete case.
I'd be much happier with this PR if collapsibility were only introduced where the schema is circular, and leaves the existing, simplistic UX for the common case. Of course, you might also want to extract the idea of collapsibility and try to get it reviewed separately, but I understand if that isn't exactly relevant to your goal.
@@ -45,7 +45,7 @@ class ObjectField extends Component { | |||
onBlur, | |||
} = this.props; | |||
const { definitions, fields, formContext } = this.props.registry; | |||
const { SchemaField, TitleField, DescriptionField } = fields; | |||
const { ObjectPropertyField, TitleField, DescriptionField } = fields; |
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.
Consider putting this comment as JSDoc on the ObjectPropertyField
class.
const isDefault = | ||
!(name in formData) || | ||
typeof Object.getOwnPropertyDescriptor(formData, name).get === | ||
"function"; |
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.
Yes, I have to say this is super ugly, and passing explicit formData
and defaults
would be better in a perfect world. On the other hand, I'm not sure I want to separate them as part of this PR. I think extracting this into a variable called isDefault
is good enough, although maybe you could add a FIXME comment explaining this.
@@ -14,6 +15,7 @@ export default { | |||
DescriptionField, | |||
NumberField, | |||
ObjectField, | |||
ObjectPropertyField, |
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 we really need to expose ObjectPropertyField
?
</div> | ||
); | ||
} else { | ||
return <button onClick={this.show}>ADD {title}</button>; |
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 pretty nervous about the UX in general. I know that @n1k0 took a swing at collapsibility before in #268 and wasn't able to find a UX that he found felt right for most use cases, and this feels like a similar situation to me. Can we add this only if we are in a circular-schema situation? (N.B. I haven't actually tried running the code so this may not be as intrusive as I imagine it being.)
properties: { | ||
qux: { type: "string" }, | ||
}, | ||
}, | ||
}, | ||
type: "object", | ||
required: ["foo"], |
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.
These are great examples of what I don't think should be necessary.
@eggyal now that the actual problem in #556 has been fixed, I guess the question is whether circular references should be supported. How do you envision a form should look like when it's created from a schema such as this?
|
An object instance is valid even if properties are omitted (unless they are explicitly So, the UI must provide some means of omitting optional properties. Moreover, optional properties within a circular path must not be rendered until they actually exist in the document instance else the rendering will never terminate: so the UI must provide some means of adding optional properties that do not yet exist in the document. I provided in my original PR a UI through which all properties (whether on circular paths or otherwise) would be omitted from the form until actually required (whether explicitly by schema or implicitly by document instance), but @glasserc (quite rightly) felt it would impair the UX for simple cases. Taking that into consideration, perhaps RJSF needs to:
This would still render entire forms in the simple cases (whilst also allowing optional properties within them to be properly deleted/omitted from the document, rather than merely left empty) but would furthermore support circularities. Items 3 and 4 were implement in this PR, albeit could undoubtedly be made prettier. Item 2 was half implemented, insofar as this PR only rendered a property if it was required irrespective of whether it was on a circular path. So, the main suggestion here is really to identify the circular paths and add that as a discriminant in whether a field is rendered by default or not. |
Closing this PR due to inactivity. It would be best to continue discussion of this issue in a new issue. |
Reasons for making this change
Fixes #556
Checklist
npm run cs-format
on my branch to conform my code to prettier coding style