Skip to content

Commit 3ddc2a0

Browse files
authored
fix(ui): unflattening json objects containing keys with periods (#6839)
## Description Fixes an issue where the `unflatten` function would also unflatten json objects when they contained a `.` in one of their keys V2 PR [here](#6834)
1 parent 2c4da93 commit 3ddc2a0

File tree

9 files changed

+185
-43
lines changed

9 files changed

+185
-43
lines changed

packages/ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@
111111
"bson-objectid": "2.0.4",
112112
"date-fns": "3.3.1",
113113
"deep-equal": "2.2.2",
114-
"flatley": "5.2.0",
114+
"is-buffer": "^2.0.5",
115115
"md5": "2.3.0",
116116
"object-to-formdata": "4.5.1",
117117
"qs": "6.11.2",

packages/ui/src/forms/Form/getDataByPath.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { FormState } from 'payload'
22

3-
import flatleyImport from 'flatley'
4-
const { unflatten } = flatleyImport
3+
import { unflatten } from '../../utilities/unflatten.js'
54

65
export const getDataByPath = <T = unknown>(fields: FormState, path: string): T => {
76
const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1)

packages/ui/src/forms/Form/getSiblingData.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import type { Data, FormState } from 'payload'
22

3-
import flatleyImport from 'flatley'
4-
const { unflatten } = flatleyImport
5-
63
import { reduceFieldsToValues } from '../../utilities/reduceFieldsToValues.js'
4+
import { unflatten } from '../../utilities/unflatten.js'
75

86
export const getSiblingData = (fields: FormState, path: string): Data => {
97
if (!fields) return null
@@ -39,5 +37,5 @@ export const getSiblingData = (fields: FormState, path: string): Data => {
3937
}
4038
})
4139

42-
return unflatten(siblingFields, { safe: true })
40+
return unflatten(siblingFields)
4341
}

packages/ui/src/utilities/reduceFieldsToValues.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Data, FormState } from 'payload'
22

3-
import flatleyImport from 'flatley'
4-
const { unflatten: flatleyUnflatten } = flatleyImport
3+
import { unflatten as flatleyUnflatten } from './unflatten.js'
54
/**
65
* Reduce flattened form fields (Fields) to just map to the respective values instead of the full FormField object
76
*
@@ -25,7 +24,7 @@ export const reduceFieldsToValues = (
2524
})
2625

2726
if (unflatten) {
28-
data = flatleyUnflatten(data, { safe: true })
27+
data = flatleyUnflatten(data)
2928
}
3029

3130
return data

packages/ui/src/utilities/reduceFieldsToValuesWithValidation.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Data, FormState } from 'payload'
22

3-
import flatleyImport from 'flatley'
4-
const { unflatten: flatleyUnflatten } = flatleyImport
3+
import { unflatten as flatleyUnflatten } from './unflatten.js'
54

65
type ReturnType = {
76
data: Data
@@ -35,7 +34,7 @@ export const reduceFieldsToValuesWithValidation = (
3534
})
3635

3736
if (unflatten) {
38-
state.data = flatleyUnflatten(state.data, { safe: true })
37+
state.data = flatleyUnflatten(state.data)
3938
}
4039

4140
return state
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright (c) 2014, Hugh Kennedy
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6+
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8+
* 3. Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9+
*
10+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11+
*/
12+
13+
import isBuffer from 'is-buffer'
14+
15+
interface Opts {
16+
delimiter?: string
17+
object?: any
18+
overwrite?: boolean
19+
recursive?: boolean
20+
}
21+
22+
export const unflatten = (target, opts?: Opts) => {
23+
opts = opts || {}
24+
25+
const delimiter = opts.delimiter || '.'
26+
const overwrite = opts.overwrite || false
27+
const recursive = opts.recursive || false
28+
const result = {}
29+
30+
const isbuffer = isBuffer(target)
31+
32+
if (isbuffer || Object.prototype.toString.call(target) !== '[object Object]') {
33+
return target
34+
}
35+
36+
// safely ensure that the key is an integer.
37+
const getkey = (key) => {
38+
const parsedKey = Number(key)
39+
return isNaN(parsedKey) || key.indexOf('.') !== -1 || opts.object ? key : parsedKey
40+
}
41+
42+
const sortedKeys = Object.keys(target).sort((keyA, keyB) => keyA.length - keyB.length)
43+
44+
sortedKeys.forEach((key) => {
45+
const split = key.split(delimiter)
46+
let key1 = getkey(split.shift())
47+
let key2 = getkey(split[0])
48+
let recipient = result
49+
50+
while (key2 !== undefined) {
51+
if (key1 === '__proto__') {
52+
return
53+
}
54+
55+
const type = Object.prototype.toString.call(recipient[key1])
56+
const isobject = type === '[object Object]' || type === '[object Array]'
57+
58+
// do not write over falsey, non-undefined values if overwrite is false
59+
if (!overwrite && !isobject && typeof recipient[key1] !== 'undefined') {
60+
return
61+
}
62+
63+
if ((overwrite && !isobject) || (!overwrite && recipient[key1] == null)) {
64+
recipient[key1] = typeof key2 === 'number' && !opts.object ? [] : {}
65+
}
66+
67+
recipient = recipient[key1]
68+
69+
if (split.length > 0) {
70+
key1 = getkey(split.shift())
71+
key2 = getkey(split[0])
72+
}
73+
}
74+
75+
// unflatten again for 'messy objects'
76+
recipient[key1] = recursive ? unflatten(target[key], opts) : target[key]
77+
})
78+
79+
return result
80+
}

pnpm-lock.yaml

Lines changed: 62 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fields/collections/JSON/index.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@ import type { CollectionConfig } from 'payload'
22

33
import { jsonFieldsSlug } from '../../slugs.js'
44

5-
type JSONField = {
6-
createdAt: string
7-
id: string
8-
json?: any
9-
updatedAt: string
10-
}
11-
125
const JSON: CollectionConfig = {
136
slug: jsonFieldsSlug,
147
access: {
@@ -34,6 +27,16 @@ const JSON: CollectionConfig = {
3427
uri: 'a://b/foo.json',
3528
},
3629
},
30+
{
31+
name: 'group',
32+
type: 'group',
33+
fields: [
34+
{
35+
name: 'jsonWithinGroup',
36+
type: 'json',
37+
},
38+
],
39+
},
3740
],
3841
versions: {
3942
maxPerDoc: 1,

0 commit comments

Comments
 (0)