Control scope should be able to reference the first element in array

Today it is impossible to have a Control scope to select a field from a selected element inside an Array. The scope of a Control only references a path within the Schema, and therefore cannot select for a positional element within the data. To me this seems like a limitation to JSON forms, one which could potentially have a simple solution.

As background, many of our entities contain properties that are arrays which always contain one element. The reason is because the child entries are effective dated, where many records can exist but when viewing as of a particular date there will always be precisely one entry, whatever the active entry is as of the requested date. The most common use case is to show the current active entry as of today.

I can work around the problem by adding a transformation layer that mutates the data/schema so that these arrays become simple objects instead. But I don’t really consider that a good solution.

Example Data:

{"details": [{"firstName": "John"}]}

Example Schema:

{"type": "object","properties": {"details": {"type": "array","items": {"type": "object","properties": {"firstName": {"type": "string"}}}}}}

Here is what I would think the uischema should be:

{"type":"Control",scope:"#/properties/details/items/0/firstName"}

However, that doesn’t work because the /0 is not a path within the uischema.

Could this be handled in consistent way, such that /items/0 can be resolved to the items schema, but the data is resolved to the first element in the array?

Hi @i832513,

I think the best way to support this feature is not to modify the scope itself but add an additional property. At the moment the scope is mostly a simple JSON Pointer resolving to the (sub) JSON Schema. “Mostly” because we already have some magic regarding combinators and if/else in there to support conditional schemas.

The additional property could for example be dataScope, path, overridePath or something similar to that and then resolve within the data object instead of against the JSON Schema. So in your case that could look like this:

type: 'Control',
scope: '#/properties/details/items/firstName',
path: '/details/0/firstName'

On JSON Forms side this would then be quite easy: Instead of determining the path by converting the scope, we could just take the hard coded path out of the UI Schema path if it exists.

Note that you technically don’t need any support from within JSON Forms for this, you can implement this feature via custom renderers on your side. Would this be feasible for you?

Hi, thanks for your quick feedback here. I could do it like that and update our renderer set; however, I think it would be nice to update the core mapStateToControlProps in renderer.ts to do this from @jsonforms/core out of the box and add it to the Types of a ControlElement that path is an optional property.

For my purpose I could create a new with function to override incoming props like below:

import { withJsonFormsControlProps } from "@jsonforms/react";
import { withDataFromPathProp } from "../withControlPathProp";

export const TextControl = withJsonFormsControlProps(withControlPathProp(TextControl));

The wrapper with function could read the uischema and see if there is a path setting, if so it would override the incoming path and data props to pass to the wrapped component.

Hi @i832513,

I think your suggested solution with the additional wrapping is the best one. Especially if you are already using a lot of custom renderers, then this is hardly any additional effort.

I don’t think that we should add this in general to JSON Forms itself as it’s a special use case. Instead I would be in favor of a generic “transform” mechanism to modify props determined by JSON Forms before they are handed over to the respective renderer. Then not only this use case, but many more, could be easily handled with the same mechanism.

I was able to get it to work; however, I did need to do some extra things to correct the “.errors” prop to get a translated error at the changed path which the “mapStateToControlProps” function used the wrong path before being changed by the uischema.path value. Not a big change but a little awkward because the errors gets translated once at the wrong path and then overwritten later by the props transformation lower down.

I see, yes, that’s a bit clunky and this problem would even persist with the proposed “transform” mechanism. For now you could choose to opt out of reusing the mapper of JSON Forms but just copy and modify it, however this comes with its own downsides as you will miss out on any future bug fixes and/or features in the bindings.