Skip to content

Commit

Permalink
feat!: add json() method and remove default object/array coercion
Browse files Browse the repository at this point in the history
BREAKING CHANGE: object and array schema no longer parse JSON strings by default, nor do they return `null` for invalid casts.

```ts
object().json().cast('{}')
array().json().cast('[]')
```
to mimic the previous behavior
  • Loading branch information
jquense committed Dec 29, 2021
1 parent e7ac9f6 commit 94b73c4
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 44 deletions.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1420,24 +1420,28 @@ The default `cast` behavior for `array` is: [`JSON.parse`](https://developer.moz

Failed casts return: `null`;

#### `array.of(type: Schema): Schema`
#### `array.of(type: Schema): this`

Specify the schema of array elements. `of()` is optional and when omitted the array schema will
not validate its contents.

#### `array.length(length: number | Ref, message?: string | function): Schema`
#### `array.json(): this`

Attempt to parse input string values as JSON using [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse).

#### `array.length(length: number | Ref, message?: string | function): this`

Set a specific length requirement for the array. The `${length}` interpolation can be used in the `message` argument.

#### `array.min(limit: number | Ref, message?: string | function): Schema`
#### `array.min(limit: number | Ref, message?: string | function): this`

Set a minimum length limit for the array. The `${min}` interpolation can be used in the `message` argument.

#### `array.max(limit: number | Ref, message?: string | function): Schema`
#### `array.max(limit: number | Ref, message?: string | function): this`

Set a maximum length limit for the array. The `${max}` interpolation can be used in the `message` argument.

#### `array.ensure(): Schema`
#### `array.ensure(): this`

Ensures that the value is an array, by setting the default to `[]` and transforming `null` and `undefined`
values to an empty array as well. Any non-empty, non-array value will be wrapped in an array.
Expand Down Expand Up @@ -1476,9 +1480,7 @@ yup.object({
});
```

The default `cast` behavior for `object` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)

Failed casts return: `null`;
object schema do not have any default transforms applied.

#### Object schema defaults

Expand Down Expand Up @@ -1546,6 +1548,10 @@ object({
});
```

#### `object.json(): this`

Attempt to parse input string values as JSON using [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse).

#### `object.concat(schemaB: ObjectSchema): ObjectSchema`

Creates a object schema, by applying all settings and fields from `schemaB` to the base, producing a new schema.
Expand Down
24 changes: 7 additions & 17 deletions src/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from './util/types';
import Schema, { SchemaInnerTypeDescription, SchemaSpec } from './schema';
import { ResolveOptions } from './Condition';
import parseJson from 'parse-json';

export type RejectorFn = (
value: any,
Expand Down Expand Up @@ -55,24 +56,8 @@ export default class ArraySchema<
},
});

// `undefined` specifically means uninitialized, as opposed to
// "no subtype"
// `undefined` specifically means uninitialized, as opposed to "no subtype"
this.innerType = type;

this.withMutation(() => {
this.transform((values, _, ctx) => {
if (!ctx.spec.coarce) return values;
if (typeof values === 'string') {
try {
values = JSON.parse(values);
} catch (err) {
values = null;
}
}

return ctx.isType(values) ? values : null;
});
});
}

private get _subType() {
Expand Down Expand Up @@ -170,6 +155,11 @@ export default class ArraySchema<
return next;
}

/** Parse an input JSON string to an object */
json() {
return this.transform(parseJson);
}

concat<IT, IC, ID, IF extends Flags, IIn extends Maybe<IT[]>>(
schema: ArraySchema<IT, IC, ID, IF, IIn>,
): ArraySchema<
Expand Down
18 changes: 6 additions & 12 deletions src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
PartialDeep,
TypeFromShape,
} from './util/objectTypes';
import parseJson from './util/parseJson';

export type { AnyObject };

Expand Down Expand Up @@ -124,18 +125,6 @@ export default class ObjectSchema<
});

this.withMutation(() => {
this.transform((value, _raw, ctx) => {
if (typeof value === 'string') {
try {
value = JSON.parse(value);
} catch (err) {
value = null;
}
}
if (ctx.isType(value)) return value;
return null;
});

if (spec) {
this.shape(spec as any);
}
Expand Down Expand Up @@ -442,6 +431,11 @@ export default class ObjectSchema<
});
}

/** Parse an input JSON string to an object */
json() {
return this.transform(parseJson);
}

noUnknown(message?: Message): this;
noUnknown(noAllow: boolean, message?: Message): this;
noUnknown(noAllow: Message | boolean = true, message = locale.noUnknown) {
Expand Down
18 changes: 18 additions & 0 deletions src/util/parseJson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { AnySchema } from '..';
import type { TransformFunction } from '../types';

const parseJson: TransformFunction<any> = (value, _, ctx: AnySchema<any>) => {
if (typeof value !== 'string') {
return value;
}

let parsed = value;
try {
parsed = JSON.parse(value);
} catch (err) {
/* */
}
return ctx.isType(parsed) ? parsed : value;
};

export default parseJson;
8 changes: 4 additions & 4 deletions test/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { string, number, object, array, StringSchema, AnySchema } from '../src';
describe('Array types', () => {
describe('casting', () => {
it('should parse json strings', () => {
expect(array().cast('[2,3,5,6]')).toEqual([2, 3, 5, 6]);
expect(array().json().cast('[2,3,5,6]')).toEqual([2, 3, 5, 6]);
});

it('should return null for failed casts', () => {
expect(array().cast('asfasf', { assert: false })).toBeNull();
it('should failed casts return input', () => {
expect(array().cast('asfasf', { assert: false })).toEqual('asfasf');

expect(array().cast(null, { assert: false })).toBeNull();
expect(array().cast('{}', { assert: false })).toEqual('{}');
});

it('should recursively cast fields', () => {
Expand Down
8 changes: 5 additions & 3 deletions test/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ describe('Object types', () => {
});

it('should parse json strings', () => {
expect(object({ hello: number() }).cast('{ "hello": "5" }')).toEqual({
expect(
object({ hello: number() }).json().cast('{ "hello": "5" }'),
).toEqual({
hello: 5,
});
});

it('should return null for failed casts', () => {
expect(object().cast('dfhdfh', { assert: false })).toBeNull();
it('should return input for failed casts', () => {
expect(object().cast('dfhdfh', { assert: false })).toBe('dfhdfh');
});

it('should recursively cast fields', () => {
Expand Down

0 comments on commit 94b73c4

Please sign in to comment.