Skip to content

Commit

Permalink
Json field updates (#6607)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored Sep 20, 2021
1 parent 232c512 commit 42268ee
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-apples-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': major
---

Removed `isRequired` and `defaultValue` can no longer be dynamic in the `json` field. If you were using `isRequired`, the same behaviour can be re-created with the `validateInput` hook.
5 changes: 3 additions & 2 deletions docs/pages/docs/apis/fields.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ export default config({
A `json` field represents a JSON blob.
Currently the `json` field is non-orderable and non-filterable.

- `isRequired` (default: `false`): If `true` then this field can never be set to `null`.
- `defaultValue` (default: `null`): Can be set to any JSON value.
This value will be used for the field when creating items if no explicit value is set.

```typescript
import { config, list } from '@keystone-next/keystone';
Expand All @@ -234,7 +235,7 @@ export default config({
ListName: list({
fields: {
fieldName: json({
isRequired: true,
defaultValue: { something: true },
}),
/* ... */
},
Expand Down
13 changes: 6 additions & 7 deletions examples/json/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ You can also access a Apollo Sandbox at [localhost:3000/api/graphql](http://loca

## Features

This example implements a `Packages` list. In this field we specify a `pkgjson` field which is a _required_ [`json` field type](https://keystonejs.com/docs/apis/fields#json).
This example implements a `Packages` list. In this field we specify a `pkgjson` field which is a [`json` field type](https://keystonejs.com/docs/apis/fields#json).
This accepts any valid JSON including:

- string node
- number node
- array node
- object node

As this is required, both inputting `null` in the Admin UI as well as leaving the input empty are not accepted. However try removing the `isRequired` config option from the field config. In doing so, you'll notice that both inputting the string `null` as well as an empty field will result in a `null` database value being stored.
- string
- number
- array
- object
- null

The JSON field type stores its value in the `jsonb` format, as specified by Prisma. However if `sqlite` is specified as the database type instead, then the value is stored as a `string`.
2 changes: 1 addition & 1 deletion examples/json/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const lists = {
Package: list({
fields: {
label: text({ isRequired: true }),
pkgjson: json({ isRequired: true }),
pkgjson: json(),
isPrivate: checkbox(),
ownedBy: relationship({ ref: 'Person.packages', many: false }),
},
Expand Down
48 changes: 31 additions & 17 deletions packages/keystone/src/fields/types/json/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
BaseGeneratedListTypes,
FieldDefaultValue,
JSONValue,
FieldTypeFunc,
CommonFieldConfig,
Expand All @@ -11,32 +10,47 @@ import { resolveView } from '../../resolve-view';

export type JsonFieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> =
CommonFieldConfig<TGeneratedListTypes> & {
defaultValue?: FieldDefaultValue<JSONValue, TGeneratedListTypes>;
isRequired?: boolean;
defaultValue?: JSONValue;
};

export const json =
<TGeneratedListTypes extends BaseGeneratedListTypes>({
isRequired,
defaultValue,
defaultValue = null,
...config
}: JsonFieldConfig<TGeneratedListTypes> = {}): FieldTypeFunc =>
meta => {
if ((config as any).isIndexed === 'unique') {
throw Error("isIndexed: 'unique' is not a supported option for field type json");
}

return jsonFieldTypePolyfilledForSQLite(meta.provider, {
...config,
input: {
create: { arg: graphql.arg({ type: graphql.JSON }) },
update: { arg: graphql.arg({ type: graphql.JSON }) },
},
output: graphql.field({ type: graphql.JSON }),
views: resolveView('json/views'),
__legacy: {
defaultValue,
isRequired,
const resolve = (val: JSONValue | undefined) =>
val === null && meta.provider === 'postgresql' ? 'DbNull' : val;

return jsonFieldTypePolyfilledForSQLite(
meta.provider,
{
...config,
input: {
create: {
arg: graphql.arg({ type: graphql.JSON }),
resolve(val) {
return resolve(val === undefined ? defaultValue : val);
},
},
update: { arg: graphql.arg({ type: graphql.JSON }), resolve },
},
output: graphql.field({ type: graphql.JSON }),
views: resolveView('json/views'),
getAdminMeta: () => ({ defaultValue }),
},
});
{
default:
defaultValue === null
? undefined
: {
kind: 'literal',
value: JSON.stringify(defaultValue),
},
}
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { json } from '..';
import { KeystoneContext } from '../../../../types';

export const name = 'Json';
export const typeFunction = json;
Expand All @@ -10,18 +9,19 @@ export const exampleValue2 = () => ({
b: [],
});
export const supportsUnique = false;
export const skipRequiredTest = true;
export const fieldName = 'testField';

export const getTestFields = () => ({ testField: json() });

export const initItems = (_: any, context: KeystoneContext) => {
export const initItems = () => {
return [
{ name: 'a', testField: { a: [] } },
{ name: 'b', testField: { b: 'string' } },
{ name: 'c', testField: { c: 42 } },
{ name: 'd', testField: { d: { i: 25 } } },
{ name: 'e', testField: { e: null } },
{ name: 'f', testField: context.prisma.DbNull },
{ name: 'f', testField: null },
{ name: 'g' },
];
};
Expand Down
9 changes: 7 additions & 2 deletions packages/keystone/src/fields/types/json/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FieldController,
FieldControllerConfig,
FieldProps,
JSONValue,
} from '../../../../types';
import { CellContainer, CellLink } from '../../../../admin-ui/components';

Expand All @@ -26,6 +27,7 @@ export const Field = ({
{onChange ? (
<Stack>
<TextArea
css={{ fontFamily: 'monospace' }}
autoFocus={autoFocus}
onChange={event => onChange(event.target.value)}
value={value}
Expand Down Expand Up @@ -59,14 +61,17 @@ export const CardValue: CardValueComponent = ({ item, field }) => {
);
};

type Config = FieldControllerConfig;
type Config = FieldControllerConfig<{ defaultValue: JSONValue }>;

export const controller = (config: Config): FieldController<string, string> => {
return {
path: config.path,
label: config.label,
graphqlSelection: config.path,
defaultValue: '',
defaultValue:
config.fieldMeta.defaultValue === null
? ''
: JSON.stringify(config.fieldMeta.defaultValue, null, 2),
validate: value => {
if (!value) return true;
try {
Expand Down

0 comments on commit 42268ee

Please sign in to comment.