Skip to content

Commit 94b73c4

Browse files
committed
feat!: add json() method and remove default object/array coercion
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
1 parent e7ac9f6 commit 94b73c4

File tree

6 files changed

+54
-44
lines changed

6 files changed

+54
-44
lines changed

README.md

+14-8
Original file line numberDiff line numberDiff line change
@@ -1420,24 +1420,28 @@ The default `cast` behavior for `array` is: [`JSON.parse`](https://developer.moz
14201420

14211421
Failed casts return: `null`;
14221422

1423-
#### `array.of(type: Schema): Schema`
1423+
#### `array.of(type: Schema): this`
14241424

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

1428-
#### `array.length(length: number | Ref, message?: string | function): Schema`
1428+
#### `array.json(): this`
1429+
1430+
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).
1431+
1432+
#### `array.length(length: number | Ref, message?: string | function): this`
14291433

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

1432-
#### `array.min(limit: number | Ref, message?: string | function): Schema`
1436+
#### `array.min(limit: number | Ref, message?: string | function): this`
14331437

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

1436-
#### `array.max(limit: number | Ref, message?: string | function): Schema`
1440+
#### `array.max(limit: number | Ref, message?: string | function): this`
14371441

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

1440-
#### `array.ensure(): Schema`
1444+
#### `array.ensure(): this`
14411445

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

1479-
The default `cast` behavior for `object` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
1480-
1481-
Failed casts return: `null`;
1483+
object schema do not have any default transforms applied.
14821484

14831485
#### Object schema defaults
14841486

@@ -1546,6 +1548,10 @@ object({
15461548
});
15471549
```
15481550

1551+
#### `object.json(): this`
1552+
1553+
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).
1554+
15491555
#### `object.concat(schemaB: ObjectSchema): ObjectSchema`
15501556

15511557
Creates a object schema, by applying all settings and fields from `schemaB` to the base, producing a new schema.

src/array.ts

+7-17
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
} from './util/types';
2626
import Schema, { SchemaInnerTypeDescription, SchemaSpec } from './schema';
2727
import { ResolveOptions } from './Condition';
28+
import parseJson from 'parse-json';
2829

2930
export type RejectorFn = (
3031
value: any,
@@ -55,24 +56,8 @@ export default class ArraySchema<
5556
},
5657
});
5758

58-
// `undefined` specifically means uninitialized, as opposed to
59-
// "no subtype"
59+
// `undefined` specifically means uninitialized, as opposed to "no subtype"
6060
this.innerType = type;
61-
62-
this.withMutation(() => {
63-
this.transform((values, _, ctx) => {
64-
if (!ctx.spec.coarce) return values;
65-
if (typeof values === 'string') {
66-
try {
67-
values = JSON.parse(values);
68-
} catch (err) {
69-
values = null;
70-
}
71-
}
72-
73-
return ctx.isType(values) ? values : null;
74-
});
75-
});
7661
}
7762

7863
private get _subType() {
@@ -170,6 +155,11 @@ export default class ArraySchema<
170155
return next;
171156
}
172157

158+
/** Parse an input JSON string to an object */
159+
json() {
160+
return this.transform(parseJson);
161+
}
162+
173163
concat<IT, IC, ID, IF extends Flags, IIn extends Maybe<IT[]>>(
174164
schema: ArraySchema<IT, IC, ID, IF, IIn>,
175165
): ArraySchema<

src/object.ts

+6-12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
PartialDeep,
3030
TypeFromShape,
3131
} from './util/objectTypes';
32+
import parseJson from './util/parseJson';
3233

3334
export type { AnyObject };
3435

@@ -124,18 +125,6 @@ export default class ObjectSchema<
124125
});
125126

126127
this.withMutation(() => {
127-
this.transform((value, _raw, ctx) => {
128-
if (typeof value === 'string') {
129-
try {
130-
value = JSON.parse(value);
131-
} catch (err) {
132-
value = null;
133-
}
134-
}
135-
if (ctx.isType(value)) return value;
136-
return null;
137-
});
138-
139128
if (spec) {
140129
this.shape(spec as any);
141130
}
@@ -442,6 +431,11 @@ export default class ObjectSchema<
442431
});
443432
}
444433

434+
/** Parse an input JSON string to an object */
435+
json() {
436+
return this.transform(parseJson);
437+
}
438+
445439
noUnknown(message?: Message): this;
446440
noUnknown(noAllow: boolean, message?: Message): this;
447441
noUnknown(noAllow: Message | boolean = true, message = locale.noUnknown) {

src/util/parseJson.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { AnySchema } from '..';
2+
import type { TransformFunction } from '../types';
3+
4+
const parseJson: TransformFunction<any> = (value, _, ctx: AnySchema<any>) => {
5+
if (typeof value !== 'string') {
6+
return value;
7+
}
8+
9+
let parsed = value;
10+
try {
11+
parsed = JSON.parse(value);
12+
} catch (err) {
13+
/* */
14+
}
15+
return ctx.isType(parsed) ? parsed : value;
16+
};
17+
18+
export default parseJson;

test/array.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { string, number, object, array, StringSchema, AnySchema } from '../src';
33
describe('Array types', () => {
44
describe('casting', () => {
55
it('should parse json strings', () => {
6-
expect(array().cast('[2,3,5,6]')).toEqual([2, 3, 5, 6]);
6+
expect(array().json().cast('[2,3,5,6]')).toEqual([2, 3, 5, 6]);
77
});
88

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

12-
expect(array().cast(null, { assert: false })).toBeNull();
12+
expect(array().cast('{}', { assert: false })).toEqual('{}');
1313
});
1414

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

test/object.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ describe('Object types', () => {
3636
});
3737

3838
it('should parse json strings', () => {
39-
expect(object({ hello: number() }).cast('{ "hello": "5" }')).toEqual({
39+
expect(
40+
object({ hello: number() }).json().cast('{ "hello": "5" }'),
41+
).toEqual({
4042
hello: 5,
4143
});
4244
});
4345

44-
it('should return null for failed casts', () => {
45-
expect(object().cast('dfhdfh', { assert: false })).toBeNull();
46+
it('should return input for failed casts', () => {
47+
expect(object().cast('dfhdfh', { assert: false })).toBe('dfhdfh');
4648
});
4749

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

0 commit comments

Comments
 (0)