From 90f3f8b0cbff140680410d994d380e3ae5e863d3 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 15 Dec 2023 11:44:36 +0100 Subject: [PATCH] fix: use prototypeless objects for field-to-type mappings (#212) Fixes: https://github.com/mongodb-js/mongodb-schema/issues/211 --- src/schema-analyzer.ts | 14 +++++++------- test/basic.test.ts | 7 +++++-- test/mixed-type-nested.test.ts | 5 +++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/schema-analyzer.ts b/src/schema-analyzer.ts index 89707f1..29817bf 100644 --- a/src/schema-analyzer.ts +++ b/src/schema-analyzer.ts @@ -1,6 +1,6 @@ import Reservoir from 'reservoir'; -import type { +import { Document, ObjectId, MinKey, @@ -310,7 +310,7 @@ function simplifiedSchema(fields: SchemaAnalysisFieldsMap): SimplifiedSchema { } function finalizeDocumentFieldSchema(fieldMap: SchemaAnalysisFieldsMap): SimplifiedSchema { - const fieldSchema: SimplifiedSchema = {}; + const fieldSchema: SimplifiedSchema = Object.create(null); Object.values(fieldMap).forEach((field: SchemaAnalysisField) => { const fieldTypes = finalizeSchemaFieldTypes(field.types); @@ -437,7 +437,7 @@ export class SchemaAnalyzer { options: SchemaParseOptions; documentsAnalyzed = 0; schemaAnalysisRoot: SchemaAnalysisRoot = { - fields: {}, + fields: Object.create(null), count: 0 }; @@ -464,7 +464,7 @@ export class SchemaAnalyzer { this.semanticTypes = { ...Object.entries(this.semanticTypes) .filter(([k]) => enabledTypes.includes(k.toLowerCase())) - .reduce((p, [k, v]) => ({ ...p, [k]: v }), {}) + .reduce((p, [k, v]) => ({ ...p, [k]: v }), Object.create(null)) }; Object.entries(this.options.semanticTypes) @@ -512,13 +512,13 @@ export class SchemaAnalyzer { if (isArrayType(type)) { // Recurse into arrays by calling `addToType` for each element. - type.types = type.types ?? {}; + type.types = type.types ?? Object.create(null); type.lengths = type.lengths ?? []; type.lengths.push((value as BSONValue[]).length); (value as BSONValue[]).forEach((v: BSONValue) => addToType(path, v, type.types)); } else if (isDocumentType(type)) { // Recurse into nested documents by calling `addToField` for all sub-fields. - type.fields = type.fields ?? {}; + type.fields = type.fields ?? Object.create(null); Object.entries(value as Document).forEach( ([fieldName, v]) => addToField(fieldName, [...path, fieldName], v, type.fields) ); @@ -545,7 +545,7 @@ export class SchemaAnalyzer { name: fieldName, path: path, count: 0, - types: {} + types: Object.create(null) }; } const field = schema[fieldName]; diff --git a/test/basic.test.ts b/test/basic.test.ts index 6f01c5d..bd60159 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -19,7 +19,8 @@ describe('using only basic fields', function() { last_address_longitude: null, created_at: new Date(), length: 29, - 'name[]': 'Annabeth Frankie' + 'name[]': 'Annabeth Frankie', + toString: 42 } ]; @@ -38,7 +39,8 @@ describe('using only basic fields', function() { 'name', 'stats_friends', 'twitter_username', - 'name[]' + 'name[]', + 'toString' ]; assert.deepEqual(users.fields.map(v => v.name).sort(), fieldNames.sort()); }); @@ -59,5 +61,6 @@ describe('using only basic fields', function() { assert.equal(users.fields.find(v => v.name === 'name')?.type, 'String'); assert.equal(users.fields.find(v => v.name === 'stats_friends')?.type, 'Number'); assert.equal(users.fields.find(v => v.name === 'twitter_username')?.type, 'String'); + assert.equal(users.fields.find(v => v.name === 'toString')?.type, 'Number'); }); }); diff --git a/test/mixed-type-nested.test.ts b/test/mixed-type-nested.test.ts index e52551c..ef2d9b5 100644 --- a/test/mixed-type-nested.test.ts +++ b/test/mixed-type-nested.test.ts @@ -30,10 +30,11 @@ describe('mixed types nested', function() { { _id: 5, address: { - valid: true + valid: true, + toString: { value: false } } } - ]; + ] as const; let schema: Schema; let valid: SchemaField | undefined;