8000 fix: dedupe id in object · fastify/fast-json-stringify@5cf259f · GitHub
[go: up one dir, main page]

Skip to content

Commit 5cf259f

Browse files
committed
fix: dedupe id in object
1 parent 9e7e538 commit 5cf259f

File tree

2 files changed

+128
-4
lines changed

2 files changed

+128
-4
lines changed

index.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function mergeLocation (source, dest) {
5252
const arrayItemsReferenceSerializersMap = new Map()
5353
const objectReferenceSerializersMap = new Map()
5454
const schemaReferenceMap = new Map()
55+
const dedupRefsMap = new Map()
5556

5657
let ajvInstance = null
5758
let contextFunctions = null
@@ -234,7 +235,7 @@ function addPatternProperties (location) {
234235
let ppLocation = mergeLocation(location, { schema: pp[regex] })
235236
if (pp[regex].$ref) {
236237
ppLocation = refFinder(pp[regex].$ref, location)
237-
pp[regex] = ppLocation.schema
238+
pp[regex] = dedupIdsRefs(ppLocation.schema)
238239
}
239240

240241
try {
@@ -253,6 +254,8 @@ function addPatternProperties (location) {
253254
}
254255
`
255256
})
257+
dedupRefsMap.clear()
258+
256259
if (schema.additionalProperties) {
257260
code += additionalProperty(location)
258261
}
@@ -447,9 +450,10 @@ function buildCode (location, locationPath) {
447450

448451
Object.keys(schema.properties || {}).forEach((key) => {
449452
let propertyLocation = mergeLocation(location, { schema: schema.properties[key] })
450-
if (schema.properties[key].$ref) {
451-
propertyLocation = refFinder(schema.properties[key].$ref, location)
452-
schema.properties[key] = propertyLocation.schema
453+
const ref = schema.properties[key].$ref
454+
if (ref) {
455+
propertyLocation = refFinder(ref, location)
456+
schema.properties[key] = dedupIdsRefs(key, ref, propertyLocation.schema)
453457
}
454458

455459
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
@@ -484,6 +488,7 @@ function buildCode (location, locationPath) {
484488
}
485489
`
486490
})
491+
dedupRefsMap.clear()
487492

488493
for (const requiredProperty of required) {
489494
if (schema.properties && schema.properties[requiredProperty] !== undefined) continue
@@ -884,6 +889,26 @@ function dereferenceOfRefs (location, type) {
884889
return locations
885890
}
886891

892+
function dedupIdsRefs (key, refs, schema) {
893+
const arr = dedupRefsMap.get(refs) || []
894+
arr.push(key)
895+
dedupRefsMap.set(refs, [key])
896+
897+
// we dedup the same $ref only and clear the $id after the first one.
898+
// so, it can keep track the duplicate $id when it trying to resolve
899+
// difference schema.
900+
// however, it will not works for $id in nested properties.
901+
if (arr.length > 1) {
902+
// we need to check if we face the recursive schema
903+
if (!objectReferenceSerializersMap.has(schema) && !arrayItemsReferenceSerializersMap.has(schema)) {
904+
schema = clone(schema)
905+
delete schema.$id
906+
}
907+
}
908+
909+
return schema
910+
}
911+
887912
let genFuncNameCounter = 0
888913
function generateFuncName () {
889914
return 'anonymous' + genFuncNameCounter++

test/ref.test.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,3 +1249,102 @@ test('Regression 2.5.2', t => {
12491249

12501250
t.equal(output, '[{"field":"parent","sub":{"field":"joined"}}]')
12511251
})
1252+
1253+
test('ref with same id in properties', (t) => {
1254+
t.plan(2)
1255+
1256+
const externalSchema = {
1257+
ObjectId: {
1258+
$id: 'ObjectId',
1259+
type: 'string'
1260+
},
1261+
File: {
1262+
$id: 'File',
1263+
type: 'object',
1264+
properties: {
1265+
_id: { $ref: 'ObjectId' },
1266+
name: { type: 'string' },
1267+
owner: { $ref: 'ObjectId' }
1268+
}
1269+
}
1270+
}
1271+
1272+
t.test('anyOf', (t) => {
1273+
t.pla A93C n(1)
1274+
1275+
const schema = {
1276+
$id: 'Article',
1277+
type: 'object',
1278+
properties: {
1279+
_id: { $ref: 'ObjectId' },
1280+
image: {
1281+
anyOf: [
1282+
{ $ref: 'File' },
1283+
{ type: 'null' }
1284+
]
1285+
}
1286+
}
1287+
}
1288+
1289+
const stringify = build(schema, { schema: externalSchema })
1290+
const output = stringify({ _id: 'foo', image: { _id: 'bar', name: 'hello', owner: 'baz' } })
1291+
1292+
t.equal(output, '{"_id":"foo","image":{"_id":"bar","name":"hello","owner":"baz"}}')
1293+
})
1294+
1295+
t.test('oneOf', (t) => {
1296+
t.plan(1)
1297+
1298+
const schema = {
1299+
$id: 'Article',
1300+
type: 'object',
1301+
properties: {
1302+
_id: { $ref: 'ObjectId' },
1303+
image: {
1304+
oneOf: [
1305+
{ $ref: 'File' },
1306+
{ type: 'null' }
1307+
]
1308+
}
1309+
}
1310+
}
1311+
1312+
const stringify = build(schema, { schema: externalSchema })
1313+
const output = stringify({ _id: 'foo', image: { _id: 'bar', name: 'hello', owner: 'baz' } })
1314+
1315+
t.equal(output, '{"_id":"foo","image":{"_id":"bar","name":"hello","owner":"baz"}}')
1316+
})
1317+
})
1318+
1319+
test('dedup id', (t) => {
1320+
t.plan(1)
1321+
1322+
try {
1323+
const externalSchema = {
1324+
ObjectId: {
1325+
$id: 'ObjectId',
1326+
type: 'string'
1327+
}
1328+
}
1329+
1330+
const schema = {
1331+
type: 'object',
1332+
properties: {
1333+
image: {
1334+
anyOf: [
1335+
{ $ref: 'ObjectId' },
1336+
{
1337+
$id: 'ObjectId',
1338+
type: 'number'
1339+
}
1340+
]
1341+
}
1342+
}
1343+
}
1344+
1345+
build(schema, { schema: externalSchema })
1346+
t.fail('should not be here')
1347+
} catch (err) {
1348+
t.pass('should fail when $id reference to multiple schema')
1349+
}
1350+
})

0 commit comments

Comments
 (0)
0