|
1 | 1 | # Migration guide
|
2 | 2 |
|
| 3 | +## Migrating to JSON Forms 4.0 |
| 4 | + |
| 5 | +### Unified internal path handling to JSON pointers |
| 6 | + |
| 7 | +Previously, JSON Forms used two different ways to express paths: |
| 8 | + |
| 9 | +- The `scope` JSON Pointer (see [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)) paths used in UI Schemas to resolve subschemas of the provided JSON Schema |
| 10 | +- The dot-separated paths (lodash format) to resolve entries in the form-wide data object |
| 11 | + |
| 12 | +This led to confusion and prevented property names from containing dots (`.`) because lodash paths don't support escaping. |
| 13 | + |
| 14 | +The rework unifies these paths to all use the JSON Pointer format. |
| 15 | +Therefore, this breaks custom renderers that manually modify or create paths to resolve additional data. |
| 16 | +They used the dot-separated paths and need to be migrated to use JSON Pointers instead. |
| 17 | + |
| 18 | +To abstract the composition of paths away from renderers, the `Paths.compose` utility of `@jsonforms/core` should be used. |
| 19 | +It takes a valid JSON Pointer and an arbitrary number of _unencoded_ segments to append. |
| 20 | +The utility takes care of adding separators and encoding special characters in the given segments. |
| 21 | + |
| 22 | +#### How to migrate |
| 23 | + |
| 24 | +All paths that are manually composed or use the `Paths.compose` utility and add more than one segment need to be adapted. |
| 25 | + |
| 26 | +```ts |
| 27 | +import { Paths } from '@jsonforms/core'; |
| 28 | + |
| 29 | +// Some base path we want to extend. This is usually available in the renderer props |
| 30 | +// or the empty string for the whole data object |
| 31 | +const path = '/foo' |
| 32 | + |
| 33 | +// Previous: Calculate the path manually |
| 34 | +const oldManual = `${path}.foo.~bar`; |
| 35 | +// Previous: Use the Paths.compose util |
| 36 | +const oldWithUtil = Paths.compose(path, 'foo.~bar'); |
| 37 | + |
| 38 | +// Now: After the initial path, hand in each segment separately. |
| 39 | +// Segments must be unencoded. The util automatically encodes them. |
| 40 | +// In this case the ~ will be encoded. |
| 41 | +const new = Paths.compose(path, 'foo', '~bar'); |
| 42 | + |
| 43 | +// Calculate a path relative to the root data that the path is resolved against |
| 44 | +const oldFromRoot = 'nested.prop'; |
| 45 | +const newFromRoot = Paths.compose('', 'nested', 'prop'); // The empty JSON Pointer '' points to the whole data. |
| 46 | +``` |
| 47 | + |
| 48 | +#### Custom Renderer Example |
| 49 | + |
| 50 | +This example shows in a more elaborate way, how path composition might be used in a custom renderer. |
| 51 | +This example uses a custom renderer implemented for the React bindings. |
| 52 | +However, the approach is similar for all bindings. |
| 53 | + |
| 54 | +To showcase how a migration could look like, assume a custom renderer that gets handed in this data object: |
| 55 | + |
| 56 | +```ts |
| 57 | +const data = { |
| 58 | + foo: 'abc', |
| 59 | + 'b/ar': { |
| 60 | + '~': 'tilde', |
| 61 | + }, |
| 62 | + 'array~Data': ['entry1', 'entry2'], |
| 63 | +}; |
| 64 | +``` |
| 65 | + |
| 66 | +The renderer wants to resolve the `~` property to directly use it and iterate over the array and use the dispatch to render each entry. |
| 67 | + |
| 68 | +<details> |
| 69 | +<summary>Renderer code</summary> |
| 70 | + |
| 71 | +```tsx |
| 72 | +import { Paths, Resolve } from '@jsonforms/core'; |
| 73 | +import { JsonFormsDispatch } from '@jsonforms/react'; |
| 74 | + |
| 75 | +export const CustomRenderer = (props: ControlProps & WithInput) => { |
| 76 | + const { |
| 77 | + // [...] |
| 78 | + data, // The data object to be rendered. See content above |
| 79 | + path, // Path to the data object handed into this renderer |
| 80 | + schema, // JSON Schema describing this renderers data |
| 81 | + } = props; |
| 82 | + |
| 83 | + // Calculate path from the given data to the nested ~ property |
| 84 | + // You could also do this manually without the Resolve.data util |
| 85 | + const tildePath = Paths.compose('', 'b/ar', '~'); |
| 86 | + const tildeValue = Resolve.data(data, tildePath); |
| 87 | + |
| 88 | + const arrayData = data['array~Data']; |
| 89 | + // Resolve schema of array entries from this renderer's schema. |
| 90 | + const entrySchemaPath = Paths.compose( |
| 91 | + '#', |
| 92 | + 'properties', |
| 93 | + 'array~Data', |
| 94 | + 'items' |
| 95 | + ); |
| 96 | + const entrySchema = Resolve.schema(schema, entrySchemaPath); |
| 97 | + // Iterate over array~Data and dispatch for each entry |
| 98 | + // Dispatch needs the path from the root of JSON Forms's data |
| 99 | + // Thus, calculate it by extending this control's path |
| 100 | + const dispatchEntries = arrayData.map((arrayEntry, index) => { |
| 101 | + const entryPath = Paths.compose(path, 'array~Data', index); |
| 102 | + const schema = Resolve.schema(); |
| 103 | + return ( |
| 104 | + <JsonFormsDispatch |
| 105 | + key={index} |
| 106 | + schema={entrySchema} |
| 107 | + path={path} |
| 108 | + // [...] other props like cells, etc |
| 109 | + /> |
| 110 | + ); |
| 111 | + }); |
| 112 | + |
| 113 | + // [...] |
| 114 | +}; |
| 115 | +``` |
| 116 | + |
| 117 | +</details> |
| 118 | + |
3 | 119 | ## Migrating to JSON Forms 3.3
|
4 | 120 |
|
5 | 121 | ### Angular support now targets Angular 17 and Angular 18
|
|
0 commit comments