Skip to content

Commit 9453dcf

Browse files
authored
datastreams: safer and faster schema serialization (#6042)
The original implementation contained unused code and used replace calls after serializing an object to JSON. The strings could have contained parts that the replace call would have matched (while unlikely due to the content). This now directly serializes to JSON and does not have that issue anymore. That should also be faster overall.
1 parent ef608d6 commit 9453dcf

File tree

1 file changed

+33
-32
lines changed

1 file changed

+33
-32
lines changed

packages/dd-trace/src/datastreams/schemas/schema_builder.js

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ class SchemaBuilder {
1515
this.properties = 0
1616
}
1717

18+
// TODO: This is only used in tests. Let's refactor the code and stop exposing the cache.
1819
static getCache () {
1920
return CACHE
2021
}
2122

2223
static getSchemaDefinition (schema) {
23-
const noNones = convertToJsonCompatible(schema)
24-
const definition = jsonStringify(noNones)
24+
const definition = toJSON(schema)
2525
const id = fnv64(Buffer.from(definition, 'utf8')).toString()
2626
return new Schema(definition, id)
2727
}
@@ -96,42 +96,43 @@ class OpenApiComponents {
9696
}
9797
}
9898

99-
function convertToJsonCompatible (obj) {
100-
if (Array.isArray(obj)) {
101-
return obj.filter(item => item !== null).map(item => convertToJsonCompatible(item))
102-
} else if (obj && typeof obj === 'object') {
103-
const jsonObj = {}
104-
for (const [key, value] of Object.entries(obj)) {
105-
if (value !== null) {
106-
jsonObj[key] = convertToJsonCompatible(value)
99+
// This adds a single whitespace between entries without adding newlines.
100+
// This differs from JSON.stringify and is used to align with the output
101+
// in other platforms.
102+
function toJSON (value) {
103+
// eslint-disable-next-line eslint-rules/eslint-safe-typeof-object
104+
if (typeof value === 'object') {
105+
if (value === null) {
106+
return 'null'
107+
}
108+
if (Array.isArray(value)) {
109+
let result = '['
110+
for (let i = 0; i < value.length; i++) {
111+
if (i > 0) {
112+
result += ', '
113+
}
114+
result += value[i] == null ? 'null' : toJSON(value[i])
107115
}
116+
return `${result}]`
108117
}
109-
return jsonObj
110-
}
111-
return obj
112-
}
113-
114-
function convertKey (key) {
115-
if (key === 'enumValues') {
116-
return 'enum'
118+
let result = '{'
119+
for (const [key, objectValue] of Object.entries(value)) {
120+
if (objectValue != null && typeof key === 'string') {
121+
const converted = toJSON(objectValue)
122+
if (converted !== undefined) {
123+
if (result !== '{') {
124+
result += ', '
125+
}
126+
result += `"${key}": ${converted}`
127+
}
128+
}
129+
}
130+
return `${result}}`
117131
}
118-
return key
119-
}
120-
121-
function jsonStringify (obj, indent = 2) {
122-
// made to stringify json exactly similar to python / java in order for hashing to be the same
123-
const jsonString = JSON.stringify(obj, (_, value) => value, indent)
124-
return jsonString.replaceAll(/^ +/gm, ' ') // Replace leading spaces with single space
125-
.replaceAll('\n', '') // Remove newlines
126-
.replaceAll('{ ', '{') // Remove space after '{'
127-
.replaceAll(' }', '}') // Remove space before '}'
128-
.replaceAll('[ ', '[') // Remove space after '['
129-
.replaceAll(' ]', ']') // Remove space before ']'
132+
return JSON.stringify(value)
130133
}
131134

132135
module.exports = {
133136
SchemaBuilder,
134137
OpenApiSchema,
135-
convertToJsonCompatible,
136-
convertKey
137138
}

0 commit comments

Comments
 (0)