jsonforms icon indicating copy to clipboard operation
jsonforms copied to clipboard

Duplicate validation messages in primitive array renderer

Open jessevdp opened this issue 4 years ago • 4 comments
trafficstars

Describe the bug

For primitive arrays, all validation messages are combined into 1, and the top level validation for the array (is required, min amount of items) seems to duplicate itself.

The error property seems to contain:

  • Validation messages for all items in the array
  • Validation messages for the top level array itself (min/max items validation, is required, etc.)

Screenshots

Validation messages for the individual items are also in the top level array renderer:

Screenshot 2021-05-28 at 11 27 35

Top level validation messages are duplicated:

Screenshot 2021-05-28 at 11 27 09

Expected behaviour

I would expect the error property of the Array control to only contain validation for the top level. The individual items in the array could render their own validation messages.

Additionally, I would expect the "is required" or "should NOT have fewer than X items" validation messages to not be duplicated like this.

To Reproduce

Array layout renderer:

export class ArrayLayoutRenderer extends JsonFormsArrayControl implements OnInit {

  private addItem: (path: string, value: any) => () => void;
  private removeItems: (path: string, toDelete: number[]) => () => void;

  constructor(jsonFormsService: JsonFormsAngularService) {
    super(jsonFormsService);
  }

  remove(index: number): void {
    this.removeItems(this.propsPath, [index])();
  }
  add(): void {
    this.addItem(this.propsPath, createDefaultValue(this.scopedSchema))();
  }

  mapToProps(state: JsonFormsState): StatePropsOfArrayLayout {
    const props = mapStateToArrayLayoutProps(state, this.getOwnProps());
    return { ...props };
  }

  ngOnInit(): void {
    super.ngOnInit();

    const { addItem, removeItems } = mapDispatchToArrayControlProps(
      this.jsonFormsService.updateCore.bind(this.jsonFormsService)
    );
    this.addItem = addItem;
    this.removeItems = removeItems;
  }

  getProps(index: number): OwnPropsOfRenderer {
    return {
      schema: this.scopedSchema,
      path: Paths.compose(this.propsPath, `${index}`),
      uischema: controlWithoutLabel(`#`)
    };
  }

  trackByFn(index: number) {
    return index;
  }

}

Template file:

<label class="h6">{{ label }}</label>

<p class="text-hint mt-2 mb-1">{{ uischema['description'] }}</p>

<p class="caption-2 text-danger my-1">{{ error }}</p>

<button (click)="add()">Add</button>

<ng-container *ngFor="
    let item of [].constructor(data);
    let idx = index;
    trackBy: trackByFn;
    last as last
">
    <jsonforms-outlet [renderProps]="getProps(idx)"></jsonforms-outlet>
    <button (click)="remove(idx)">delete</button>
</ng-container>

Schema for this individual question:

{
  "list-question": {
    "type": "array",
    "minItems": 1,
    "items": {
      "type": "string",
      "minLength": 1
    }
  }
}

Used Setup (please complete the following information):

  • Framework: angular
  • RendererSet: custom, but based on Angular Material

jessevdp avatar May 28 '21 09:05 jessevdp

I've hacked around this a little bit for now. Basically splitting the error string, filtering out duplicates, and whitelisting the error messages I expect / want.

But this feels really hacky 😅


  get errors(): string[] {
  
    function onlyUnique(value, index, self) {
      return self.indexOf(value) === index;
    }

    return this.error.split('\n')
      .filter(onlyUnique)
      .filter(message => {
        return message.match(/should NOT have fewer than \d+ items/) != null
            || message.match(/should NOT have more than \d+ items/) != null
            || message.match(/is a required property/) != null
      })
  }


jessevdp avatar May 28 '21 10:05 jessevdp

Hi! Thanks for the extensive report! I saw similar errors in React Material, so I'm pretty sure that this is an issue in the @jsonforms/core library.

If you'd like to you can check mapStateToArrayLayoutProps. Probably there something is going wrong with the error calculation. By fixing it in the core you don't need the hacky workaround ;). If you (or someone else) provides a good contribution (including test cases) we can quickly publish a new alpha release of the next version so you could then already consume the fix while waiting for the stable release.

sdirix avatar May 28 '21 11:05 sdirix

Any update on this? The issue is still present.

DavidHenri008 avatar Jul 08 '22 19:07 DavidHenri008

Still seeing this: image

c0bra avatar Sep 15 '22 16:09 c0bra

We are experiencing similar problems while using angular-material. I did some digging around and as far as I can tell the problem is caused by the way 'child error messages' (error messages on sub elements of the array) are determined.

If you have a schema similar to this

{
    "title": "My schema",
    "type": "object",
    "properties": {
        "myArray": {
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "myArrayTwo": {
            "type": "array",
            "items": {
                "type": "string"
            }
        }
    }
}

then any error on myArrayTwo will also be reported on myArray because myArrayTwo starts with the string myArray. This is as far as I can tell a bug in reducers/core.ts/subErrorsAt. I think if you change the matchPath callback in that method to include a . at the end (because you are interested in errors on sub properties), the problem should be fixed. The implementation of subErrorsAt should then look something like this:

export const subErrorsAt = (instancePath: string, schema: JsonSchema) =>
  getErrorsAt(instancePath, schema, path => path.startsWith(instancePath + '.'));

FooBar08 avatar Nov 01 '22 07:11 FooBar08

Hi @FooBar08, Thanks for the analysis! Would you be willing to create a contribution?

sdirix avatar Nov 01 '22 22:11 sdirix