-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
fix: JSON parsing for mysql2 client library (#8319) #11659
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds connector-aware JSON hydration to the MySQL driver by tracking which connector package was loaded and branching JSON parsing accordingly; updates dependency-detection/warnings for direct driver inputs and adds functional tests (entity + test) validating JSON persistence/hydration across mysql-compatible drivers. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App as Application
participant ORM as ORM Layer
participant Driver as MysqlDriver
participant Conn as Connector (mysql | mysql2)
participant DB as MySQL
App->>ORM: find()/query()
ORM->>Driver: execute query
Driver->>Conn: send SQL
Conn->>DB: execute
DB-->>Conn: rows with JSON columns
Conn-->>Driver: result set (values)
rect rgb(245,250,255)
note over Driver: Decide JSON hydration based on loadedConnectorPackage
alt isUsingMysql2() = true
note right of Driver: mysql2 may already parse some JSON values\nFor string values: attempt JSON.parse inside try/catch
Driver->>Driver: try { JSON.parse(value) } catch { keep string }
else Using mysql
note right of Driver: previous behavior: JSON.parse for string values
Driver->>Driver: JSON.parse(value)
end
end
Driver-->>ORM: hydrated entities
ORM-->>App: results
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying typeorm with
|
| Latest commit: |
d6d2d55
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://08ef3107.typeorm.pages.dev |
| Branch Preview URL: | https://issue-8319.typeorm.pages.dev |
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/driver/mysql/MysqlDriver.ts (2)
587-601: Consider a more robust detection method for mysql2.While the current detection logic works, it relies on undocumented properties that could change in future versions of mysql2. Consider using a more stable approach.
You could simplify and make the detection more explicit:
protected isUsingMysql2(): boolean { - // mysql2 can be detected by checking for specific properties that are unique to it - // mysql2 exports a 'version' property directly on the module - // Also mysql2 has different connection promise API - return !!( - this.mysql && - (this.mysql.version || - this.mysql.createConnectionPromise || - (this.mysql.Connection && - this.mysql.Connection.prototype.execute)) - ) + // mysql2 has a distinct module name property or we can check the loaded package + // Consider checking if this.options.connectorPackage is available + const connectorPackage = this.options.connectorPackage ?? "mysql" + return connectorPackage === "mysql2" || + !!(this.mysql && this.mysql.version) // mysql2 exports version, mysql doesn't }Additionally, consider caching this result as a property since it won't change during runtime:
private _isUsingMysql2?: boolean protected isUsingMysql2(): boolean { if (this._isUsingMysql2 === undefined) { this._isUsingMysql2 = // ... detection logic } return this._isUsingMysql2 }
681-689: Consider extracting the JSON parsing logic for better testability.The nested JSON parsing logic could be extracted into a separate method for clarity and testing.
+private parseJsonValueForMysql2(value: any): any { + if (typeof value === "string") { + try { + return JSON.parse(value) + } catch { + // It's a string that's not valid JSON, which means mysql2 + // already parsed it and it's just a string value + return value + } + } + // If it's not a string, mysql2 has already parsed it correctly + return value +} prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any { // ... existing code ... } else if (columnMetadata.type === "json") { if (this.isUsingMysql2()) { - // With mysql2, only parse if it's a valid JSON string representation - // but not if it's already an object or a JSON primitive - if (typeof value === "string") { - try { - // Try to parse it - if it fails, it's already a parsed string value - const parsed = JSON.parse(value) - value = parsed - } catch { - // It's a string that's not valid JSON, which means mysql2 - // already parsed it and it's just a string value - // Keep value as is - } - } - // If it's not a string, mysql2 has already parsed it correctly + value = this.parseJsonValueForMysql2(value) } else { // Classic mysql always returns JSON as strings value = typeof value === "string" ? JSON.parse(value) : value } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (2)
package.json(1 hunks)src/driver/mysql/MysqlDriver.ts(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: tests-linux (20) / postgres (17)
- GitHub Check: tests-linux (20) / postgres (14)
- GitHub Check: tests-linux (20) / mssql
- GitHub Check: tests-linux (20) / mysql_mariadb_latest
- GitHub Check: tests-linux (20) / cockroachdb
- GitHub Check: tests-linux (20) / mysql_mariadb
- GitHub Check: tests-linux (18) / postgres (17)
- GitHub Check: tests-linux (18) / postgres (14)
- GitHub Check: tests-linux (18) / sqlite
- GitHub Check: tests-linux (18) / oracle
- GitHub Check: tests-linux (18) / sqljs
- GitHub Check: tests-linux (18) / mysql_mariadb_latest
- GitHub Check: tests-linux (18) / better-sqlite3
- GitHub Check: tests-linux (18) / mongodb
- GitHub Check: tests-linux (18) / mysql_mariadb
- GitHub Check: tests-linux (18) / mssql
- GitHub Check: tests-windows / better-sqlite3
- GitHub Check: tests-windows / sqljs
- GitHub Check: tests-windows / sqlite
🔇 Additional comments (3)
package.json (1)
129-129: Verify if upgrading better-sqlite3 is necessary for this JSON parsing fix.The PR objectives focus on fixing JSON parsing for mysql2, but better-sqlite3 (SQLite driver) has been upgraded from ^8.7.0 to ^12.2.0. This seems unrelated to the stated fix.
Can you confirm if this dependency update is intentional or if it accidentally got included from another branch? If intentional, please explain how it relates to the JSON parsing fix for mysql2.
src/driver/mysql/MysqlDriver.ts (2)
673-695: LGTM! The JSON parsing logic correctly handles both mysql and mysql2 behaviors.The implementation properly addresses the double-parsing issue:
- For mysql2: Attempts parsing with proper error handling, preserving already-parsed string primitives
- For mysql (classic): Maintains backward compatibility with existing parsing behavior
The try-catch approach safely handles JSON string primitives that mysql2 may have already parsed.
673-695: No action required — JSON null / empty-string / primitive cases are handled.preparePersistentValue uses JSON.stringify; prepareHydratedValue parses classic-mysql strings with JSON.parse and, for mysql2, only attempts JSON.parse on string inputs and falls back on the original value on parse failure; SQL NULL is returned early. See src/driver/mysql/MysqlDriver.ts (preparePersistentValue / prepareHydratedValue).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
src/driver/mysql/MysqlDriver.ts (2)
592-599: Harden mysql2 detection when a customoptions.driveris supplied.If callers inject a driver via
options.driverbut don’t setconnectorPackage,loadedConnectorPackagewill still reflect the default (“mysql”), which can misroute JSON hydration for mysql2 and reintroduce double‑parsing. Either (a) infer fromthis.options.connectorPackagefirst, falling back toloadedConnectorPackage, or (b) requireconnectorPackagewheneveroptions.driveris provided.Apply this minimal guard to prefer the explicit option when present:
- protected isUsingMysql2(): boolean { - // Check which package was actually loaded during initialization - return this.loadedConnectorPackage === "mysql2" - } + protected isUsingMysql2(): boolean { + const pkg = (this.options as any).connectorPackage ?? this.loadedConnectorPackage + return pkg === "mysql2" + }Optionally, emit a warning when
options.driveris set butconnectorPackageis absent so users don’t hit subtle JSON behavior differences.
670-693: Connector‑aware JSON hydration works; consider simplifying to a driver‑agnostic safe parse.Current branching is correct but depends on connector detection. You can avoid that coupling and keep behavior identical by parsing only when a string lexically looks like JSON and otherwise leaving it as‑is. This covers both mysql and mysql2 without needing
isUsingMysql2().Apply this refactor:
- } else if (columnMetadata.type === "json") { - // mysql2 returns JSON values already parsed, but may still be a string - // if the JSON value itself is a string (e.g., "\"hello\"") - // mysql (classic) always returns JSON as strings that need parsing - if (this.isUsingMysql2()) { - // With mysql2, only parse if it's a valid JSON string representation - // but not if it's already an object or a JSON primitive - if (typeof value === "string") { - try { - // Try to parse it - if it fails, it's already a parsed string value - const parsed = JSON.parse(value) - value = parsed - } catch { - // It's a string that's not valid JSON, which means mysql2 - // already parsed it and it's just a string value - // Keep value as is - } - } - // If it's not a string, mysql2 has already parsed it correctly - } else { - // Classic mysql always returns JSON as strings - value = typeof value === "string" ? JSON.parse(value) : value - } - } else if (columnMetadata.type === "time") { + } else if (columnMetadata.type === "json") { + if (typeof value === "string") { + const s = value.trim() + const first = s[0], last = s[s.length - 1] + const looksJson = + (first === "{" && last === "}") || + (first === "[" && last === "]") || + (first === '"' && last === '"') || + s === "true" || s === "false" || s === "null" || + /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(s) + if (looksJson) { + try { value = JSON.parse(s) } catch { /* leave as-is */ } + } + } + } else if (columnMetadata.type === "time") {test/functional/json/mysql-json-parsing/entity/JsonEntity.ts (1)
8-28: Type nits for stronger signal in tests.Using
anyeverywhere hides type regressions. Consider tightening where easy:
- jsonObject: Record<string, unknown>
- jsonArray: unknown[]
- complexJson: unknown
This remains flexible but avoids
any.- jsonObject: any + jsonObject: Record<string, unknown> @@ - jsonArray: any[] + jsonArray: unknown[] @@ - complexJson: any + complexJson: unknowntest/functional/json/mysql-json-parsing/mysql-json-parsing.test.ts (2)
11-160: Tests are clear and cover core cases; consider DRYing across connectors.Both “mysql” and “mysql2” suites duplicate the same scenarios. You could parameterize over
connectorPackageand run a single table‑driven suite to reduce maintenance.- describe("with mysql driver", () => { ... }) - describe("with mysql2 driver", () => { ... }) + ;(["mysql", "mysql2"] as const).forEach((connectorPackage) => { + describe(`with ${connectorPackage} driver`, () => { + // create connections using connectorPackage + // run the same set of tests + }) + })
311-344: Add a regression that mimics the original failure mode (string primitive serialized by connector).To mirror issue #8319 more closely, insert a JSON string primitive via raw SQL (so storage is '"hello"') and read it back through the repository to ensure no throw and correct value.
+ it("should not throw when JSON string primitive is returned serialized by the connector", () => + Promise.all( + connections.map(async (connection) => { + const qr = connection.createQueryRunner() + await qr.query(`INSERT INTO json_entity (jsonString) VALUES (CAST('\"hello\"' AS JSON))`) + const repo = connection.getRepository(JsonEntity) + const rows = await repo.find() + expect(rows[0].jsonString).to.equal("hello") + await qr.release() + }), + ))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/driver/mysql/MysqlDriver.ts(4 hunks)test/functional/json/mysql-json-parsing/entity/JsonEntity.ts(1 hunks)test/functional/json/mysql-json-parsing/mysql-json-parsing.test.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
test/functional/json/mysql-json-parsing/entity/JsonEntity.ts (2)
src/decorator/columns/PrimaryGeneratedColumn.ts (1)
PrimaryGeneratedColumn(55-119)src/decorator/columns/Column.ts (1)
Column(134-220)
test/functional/json/mysql-json-parsing/mysql-json-parsing.test.ts (1)
test/utils/test-utils.ts (3)
createTestingConnections(388-482)reloadTestingDatabases(501-506)closeTestingConnections(487-496)
src/driver/mysql/MysqlDriver.ts (1)
src/platform/PlatformTools.ts (1)
PlatformTools(17-279)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
- GitHub Check: tests-linux (20) / sqljs
- GitHub Check: tests-linux (20) / mysql_mariadb_latest
- GitHub Check: tests-linux (20) / sqlite
- GitHub Check: tests-linux (20) / sap
- GitHub Check: tests-linux (18) / postgres (14)
- GitHub Check: tests-linux (18) / sap
- GitHub Check: tests-linux (18) / postgres (17)
- GitHub Check: tests-linux (20) / oracle
- GitHub Check: tests-linux (18) / oracle
- GitHub Check: tests-linux (20) / cockroachdb
- GitHub Check: tests-linux (18) / mysql_mariadb
- GitHub Check: tests-linux (18) / mssql
- GitHub Check: tests-windows / sqljs
- GitHub Check: tests-linux (18) / mysql_mariadb_latest
- GitHub Check: tests-windows / sqlite
- GitHub Check: tests-windows / better-sqlite3
🔇 Additional comments (2)
src/driver/mysql/MysqlDriver.ts (2)
60-64: LGTM: tracking the loaded connector package is a good enabler for connector‑aware behavior.
1180-1218: Prefer explicit connectorPackage when a custom driver is injectedMysqlConnectionOptions already defines connectorPackage (src/driver/mysql/MysqlConnectionOptions.ts:110). loadDependencies unconditionally sets loadedConnectorPackage = connectorPackage (src/driver/mysql/MysqlDriver.ts:1180–1218), which can misidentify the active connector when callers inject a driver via options.driver. Repo scan found no call sites setting options.driver for mysql — external callers may still inject a custom driver.
Change suggestion — warn when driver is injected and prefer an explicit connectorPackage; mirror in fallback:
- // Successfully loaded the requested package - this.loadedConnectorPackage = connectorPackage + // Mark which package we believe is in use. + if (this.options.driver && (this.options as any).connectorPackage == null) { + PlatformTools.logWarn( + "MysqlDriver: When providing `options.driver`, also set `connectorPackage` to 'mysql' or 'mysql2' to ensure correct JSON handling.", + ) + } + this.loadedConnectorPackage = + (this.options as any).connectorPackage ?? connectorPackage- // Successfully loaded the fallback package - this.loadedConnectorPackage = fallbackConnectorPackage + this.loadedConnectorPackage = + (this.options as any).connectorPackage ?? fallbackConnectorPackage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/driver/mysql/MysqlDriver.ts (2)
592-599: Harden mysql2 detection fallbackRelying only on loadedConnectorPackage can misclassify when users pass a direct driver with a mismatched connectorPackage. Add a runtime feature check fallback.
- protected isUsingMysql2(): boolean { - // Check which package was actually loaded during initialization - return this.loadedConnectorPackage === "mysql2" - } + protected isUsingMysql2(): boolean { + // Primary signal from dependency loader + if (this.loadedConnectorPackage) { + return this.loadedConnectorPackage === "mysql2" + } + // Fallback: feature-detect mysql2 (Connection.prototype.execute exists only in mysql2) + const proto = this.mysql?.Connection?.prototype + return !!proto && typeof (proto as any).execute === "function" + }
1181-1189: Use PlatformTools logging instead of console.warnKeeps logging consistent with the rest of the codebase.
- if (this.options.driver && !this.options.connectorPackage) { - console.warn( - "Warning: MySQL driver instance provided directly without specifying connectorPackage. " + - "This may lead to unexpected JSON parsing behavior differences between mysql and mysql2. " + - "Consider explicitly setting connectorPackage: 'mysql' or 'mysql2' in your configuration.", - ) - } + if (this.options.driver && !this.options.connectorPackage) { + PlatformTools.logWarn( + "MysqlDriver", + "MySQL driver instance provided directly without specifying connectorPackage. " + + "This may lead to JSON parsing differences between 'mysql' and 'mysql2'. " + + "Consider explicitly setting connectorPackage: 'mysql' or 'mysql2'.", + ) + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/driver/mysql/MysqlDriver.ts(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/driver/mysql/MysqlDriver.ts (1)
src/platform/PlatformTools.ts (1)
PlatformTools(17-279)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: tests-linux (20) / sqljs
- GitHub Check: tests-linux (20) / sqlite
- GitHub Check: tests-linux (20) / better-sqlite3
- GitHub Check: tests-linux (20) / mysql_mariadb
- GitHub Check: tests-linux (20) / mssql
- GitHub Check: tests-linux (18) / sqljs
- GitHub Check: tests-linux (18) / postgres (17)
- GitHub Check: tests-linux (18) / mongodb
- GitHub Check: tests-linux (18) / postgres (14)
- GitHub Check: tests-linux (18) / sqlite
- GitHub Check: tests-linux (18) / mysql_mariadb_latest
- GitHub Check: tests-linux (18) / better-sqlite3
- GitHub Check: tests-linux (18) / mssql
- GitHub Check: tests-linux (18) / mysql_mariadb
- GitHub Check: tests-linux (20) / mongodb
- GitHub Check: tests-linux (20) / cockroachdb
- GitHub Check: tests-windows / sqljs
- GitHub Check: tests-windows / better-sqlite3
- GitHub Check: tests-windows / sqlite
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
src/driver/mysql/MysqlDriver.ts (3)
60-64: LGTM: explicit connector trackingThe private union type is clear and helps branch behavior safely.
1232-1232: LGTM: record fallback connectorSetting loadedConnectorPackage after fallback ensures isUsingMysql2() works consistently.
1213-1227: Tighten package heuristics for direct driver inputReplace the brittle this.mysql.version check with feature-detection (Connection.prototype.execute), warn if options.connectorPackage conflicts with the detected driver, and prefer the detected value. Verified: PlatformTools.logWarn exists at src/platform/PlatformTools.ts. Sandbox had no mysql/mysql2 installed so runtime detection couldn't be validated — run the node checks below locally.
File: src/driver/mysql/MysqlDriver.ts (around the shown snippet)
- // If driver was provided directly, try to detect which package it is - if (this.options.driver && !this.options.connectorPackage) { - // Try to detect if it's mysql2 based on unique properties - if ( - this.mysql.version || - (this.mysql.Connection && - this.mysql.Connection.prototype.execute) - ) { - this.loadedConnectorPackage = "mysql2" - } else { - this.loadedConnectorPackage = "mysql" - } - } else { - this.loadedConnectorPackage = connectorPackage - } + // If driver was provided directly, detect which package it is + if (this.options.driver) { + const isMysql2 = + !!this.mysql?.Connection?.prototype && + typeof this.mysql.Connection.prototype.execute === "function" + if ( + this.options.connectorPackage && + this.options.connectorPackage !== (isMysql2 ? "mysql2" : "mysql") + ) { + PlatformTools.logWarn( + "MysqlDriver", + `connectorPackage ('${this.options.connectorPackage}') does not match provided driver; detected '${isMysql2 ? "mysql2" : "mysql"}'. Using detected value.`, + ) + } + this.loadedConnectorPackage = isMysql2 ? "mysql2" : "mysql" + } else { + this.loadedConnectorPackage = connectorPackage + }Runtime verification to run locally:
node -e "try{const m=require('mysql');console.log('mysql keys:',Object.keys(m).slice(0,10))}catch(e){console.log('mysql not installed')}" node -e "try{const m=require('mysql2');console.log('mysql2 Connection has execute:', Boolean(m.Connection && m.Connection.prototype && m.Connection.prototype.execute))}catch(e){console.log('mysql2 not installed')}"
| // mysql2 returns JSON values already parsed, but may still be a string | ||
| // if the JSON value itself is a string (e.g., "\"hello\"") | ||
| // mysql (classic) always returns JSON as strings that need parsing | ||
| if (this.isUsingMysql2()) { | ||
| // With mysql2, only parse if it's a valid JSON string representation | ||
| // but not if it's already an object or a JSON primitive | ||
| if (typeof value === "string") { | ||
| try { | ||
| // Try to parse it - if it fails, it's already a parsed string value | ||
| const parsed = JSON.parse(value) | ||
| value = parsed | ||
| } catch { | ||
| // It's a string that's not valid JSON, which means mysql2 | ||
| // already parsed it and it's just a string value | ||
| // Keep value as is | ||
| } | ||
| } | ||
| // If it's not a string, mysql2 has already parsed it correctly | ||
| } else { | ||
| // Classic mysql always returns JSON as strings | ||
| value = typeof value === "string" ? JSON.parse(value) : value | ||
| } | ||
| } else if (columnMetadata.type === "time") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid accidental type changes for JSON string primitives like "123"
With mysql2, parsing any “valid JSON-looking” string (e.g., "123") will coerce a stored JSON string primitive "123" into number 123. Instead, only parse containers or explicitly quoted JSON strings to avoid breaking semantics.
- if (this.isUsingMysql2()) {
- // With mysql2, only parse if it's a valid JSON string representation
- // but not if it's already an object or a JSON primitive
- if (typeof value === "string") {
- try {
- // Try to parse it - if it fails, it's already a parsed string value
- const parsed = JSON.parse(value)
- value = parsed
- } catch {
- // It's a string that's not valid JSON, which means mysql2
- // already parsed it and it's just a string value
- // Keep value as is
- }
- }
- // If it's not a string, mysql2 has already parsed it correctly
- } else {
+ if (this.isUsingMysql2()) {
+ // mysql2 usually returns parsed values; only parse obvious JSON containers or quoted strings
+ if (typeof value === "string") {
+ const s = value.trim()
+ const looksLikeContainer =
+ (s.startsWith("{") && s.endsWith("}")) ||
+ (s.startsWith("[") && s.endsWith("]"))
+ const looksLikeQuotedString =
+ s.length >= 2 && s.startsWith('"') && s.endsWith('"')
+ if (looksLikeContainer || looksLikeQuotedString) {
+ try {
+ value = JSON.parse(s)
+ } catch {
+ /* keep as-is */
+ }
+ }
+ }
+ } else {
// Classic mysql always returns JSON as strings
value = typeof value === "string" ? JSON.parse(value) : value
}Would you like me to add tests covering objects, arrays, numbers, booleans, null, and string primitives (including "123") across both mysql and mysql2?
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // mysql2 returns JSON values already parsed, but may still be a string | |
| // if the JSON value itself is a string (e.g., "\"hello\"") | |
| // mysql (classic) always returns JSON as strings that need parsing | |
| if (this.isUsingMysql2()) { | |
| // With mysql2, only parse if it's a valid JSON string representation | |
| // but not if it's already an object or a JSON primitive | |
| if (typeof value === "string") { | |
| try { | |
| // Try to parse it - if it fails, it's already a parsed string value | |
| const parsed = JSON.parse(value) | |
| value = parsed | |
| } catch { | |
| // It's a string that's not valid JSON, which means mysql2 | |
| // already parsed it and it's just a string value | |
| // Keep value as is | |
| } | |
| } | |
| // If it's not a string, mysql2 has already parsed it correctly | |
| } else { | |
| // Classic mysql always returns JSON as strings | |
| value = typeof value === "string" ? JSON.parse(value) : value | |
| } | |
| } else if (columnMetadata.type === "time") { | |
| // mysql2 returns JSON values already parsed, but may still be a string | |
| // if the JSON value itself is a string (e.g., "\"hello\"") | |
| // mysql (classic) always returns JSON as strings that need parsing | |
| if (this.isUsingMysql2()) { | |
| // mysql2 usually returns parsed values; only parse obvious JSON containers or quoted strings | |
| if (typeof value === "string") { | |
| const s = value.trim() | |
| const looksLikeContainer = | |
| (s.startsWith("{") && s.endsWith("}")) || | |
| (s.startsWith("[") && s.endsWith("]")) | |
| const looksLikeQuotedString = | |
| s.length >= 2 && s.startsWith('"') && s.endsWith('"') | |
| if (looksLikeContainer || looksLikeQuotedString) { | |
| try { | |
| value = JSON.parse(s) | |
| } catch { | |
| /* keep as-is */ | |
| } | |
| } | |
| } | |
| } else { | |
| // Classic mysql always returns JSON as strings | |
| value = typeof value === "string" ? JSON.parse(value) : value | |
| } | |
| } else if (columnMetadata.type === "time") { |
Summary
Testing
It locally tested it by manually removing
node_modules/mysqlto force TypeORM to use themysql2driver. After that I set theisUsingMysql2to always return false, basically tricking the code into thinking it uses themysqllibrary, but of course it was usingmysql2. This made the tests fail, which means the tests are exactly covering the problematic parsing case and the detection process works perfectly fine.Details
The mysql2 client library automatically parses JSON values, but can still return strings for JSON string primitives (e.g.,
"hello"). The previous implementation would blindly attempt to parse any string value, which would fail for already-parsed string values from mysql2.This fix:
Fixes #8319
Summary by CodeRabbit
Bug Fixes
New Features
Tests