ajv-keywords icon indicating copy to clipboard operation
ajv-keywords copied to clipboard

Transform (trim) on nested objects via definitions is not working

Open Vitaljok opened this issue 4 years ago • 3 comments

Hello!

I have found interesting behavior of transform keyword, which is probably a bug. In short: "transform": [ "trim" ] is not working on nested objects if specified via definitions.

The schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "trimmedString": {
      "type": "string",
      "transform": ["trim"]
    }
  },
  "type": "object",
  "properties": {
    "a": {
      "$ref": "#/definitions/trimmedString"
    },
    "nested": {
      "type": "object",
      "properties": {
        "b": {
          "$ref": "#/definitions/trimmedString"
        },
        "c": {
          "type": "string",
          "transform": ["trim"]   
        }
      }
    }
  }  
}

Input data:

{
  "a": "   x   ",
  "nested": {
    "b": "   y   ",
    "c": "   z   "
  }
}

Expected results: all strings are trimmed.

Actual results: string b in nested objects is not trimmed

{
  "a": "x",
  "nested": {
    "b": "   y   ",
    "c": "z"
  }
}

My versions:

"ajv": "^7.0.3",
"ajv-formats": "^1.5.1",
"ajv-keywords": "^4.0.0",

Thanks!

Vitaljok avatar Jan 22 '21 12:01 Vitaljok

you need to activate ajv-keywords with transform as a keyword

randomhash avatar Apr 14 '21 15:04 randomhash

you need to activate ajv-keywords with transform as a keyword

Obviously, I did activated "transform" keyword. Without it I get: Error: strict mode: unknown keyword: "transform"

Here is full example (./schemas/test.json is exactly as I posted above):

import Ajv from "ajv";
import addKeywords from 'ajv-keywords';
import testSchema from './schemas/test.json';

describe('bug', () => {
  it('all strings should be trimmed', () => {
    const ajv = new Ajv({ allErrors: true });
    addKeywords(ajv, ['transform']);
    const validate = ajv.compile(testSchema);

    const data = {
      a: '   x   ',
      nested: {
        b: '   y   ',
        c: '   z   '
      }
    };

    console.log('Data before validation:', data);
    const res = validate(data);    
    console.log('Data after validation:', data);

    expect(res).toBeTrue();
    expect(data.a).toEqual('x');
    expect(data.nested.b).toEqual('y');
    expect(data.nested.c).toEqual('z');
  });
});

And the output of the spec is as follows:

Data before validation: { a: '   x   ', nested: { b: '   y   ', c: '   z   ' } }
Data after validation: { a: 'x', nested: { b: '   y   ', c: 'z' } }

Failures:
1) bug all strings should be trimmed
  Message:
    Expected '   y   ' to equal 'y'.

As you can see, trim if specified via custom definitions works for the non-nested field a, but does not not work for nested field b.

Field c is nested, but it is specified without definition.

Vitaljok avatar Apr 16 '21 10:04 Vitaljok

Hi!

I do have the same issue @Vitaljok is encoutering. I use

    "ajv": "^8.6.0",
    "ajv-keywords": "^5.0.0",
    "ajv-merge-patch": "^5.0.1"

Origin

I have a lot of big schema with nested element or $ref. Some of my schema repeat themselves: create schema has multiple required but not its update sibling. I use different method to simplify my code but all of them disable trim

  • Using $merge to remove my required options
  • Using lodash.omit, json-merge-patch or handmade deepMerge

Notice

It appears that all schema coming from a function disable trim. If I console.log the result of my deepMerge and paste it in the code, the trim seem to work.

Test:

const Ajv = require('ajv');

const ajv = new Ajv({ coerceTypes: 'array', allowUnionTypes: true });
require('ajv-merge-patch')(ajv);
require("ajv-keywords")(ajv);

const create = {
  type: 'object',
  properties: {
    body: {
      type: 'object',
      required: ['name'],
      properties: {
        name: {
          type: 'string',
          transform: ['trim']
        }
      }
    }
  }
};

const update = {
  $merge: {
    source: create,
    with: {
      properties: {
        body: {
          required: []
        }
      }
    }
  }
};

const createValidate = ajv.compile(create);
const updateValidate = ajv.compile(update);

const data = {
  body: {
    name: '    Test string to Trim        '
  }
};

function _(validate, data) {
  try {
    validate(data);
    console.log({ data });
  } catch (e) {
    console.log({ e });
  }
}

_(updateValidate, data);
_(createValidate, data);

Output:

updateCase = { data: { body: { name: '    Test string to Trim        ' } } }

createCase = { data: { body: { name: 'Test string to Trim' } } }

I can send the other methods I used, but ultimately, I am forced to paste my entire schema just to remove required.

anlerandy avatar Sep 17 '21 09:09 anlerandy