fix: resolve cross-platform SQLite JSON parsing inconsistencies
- Add platform-agnostic parseJsonField utility to handle different SQLite implementations - Web SQLite (wa-sqlite/absurd-sql) auto-parses JSON strings to objects - Capacitor SQLite returns raw strings requiring manual parsing - Update searchBoxes parsing to use new utility for consistent behavior - Fixes "[object Object] is not valid JSON" error when switching platforms - Ensures compatibility between web and mobile SQLite implementations Fixes: searchBoxes parsing errors in databaseUtil.ts Related: contactMethods field has similar issue (needs same treatment)
This commit is contained in:
892
package-lock.json
generated
892
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -135,6 +135,19 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
|
|||||||
result.columns,
|
result.columns,
|
||||||
result.values,
|
result.values,
|
||||||
)[0] as Settings;
|
)[0] as Settings;
|
||||||
|
|
||||||
|
// Debug: Check the actual data types from SQLite
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[DEBUG] Raw SQLite data types for ${defaultSettings.activeDid}:`,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
Object.entries(overrideSettings).forEach(([key, value]) => {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[DEBUG] - ${key}: ${typeof value} = ${JSON.stringify(value)}`,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const overrideSettingsFiltered = Object.fromEntries(
|
const overrideSettingsFiltered = Object.fromEntries(
|
||||||
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
|
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
|
||||||
);
|
);
|
||||||
@@ -144,17 +157,7 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
|
|||||||
|
|
||||||
// Handle searchBoxes parsing
|
// Handle searchBoxes parsing
|
||||||
if (settings.searchBoxes) {
|
if (settings.searchBoxes) {
|
||||||
try {
|
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
|
||||||
// @ts-expect-error - the searchBoxes field is a string in the DB
|
|
||||||
settings.searchBoxes = JSON.parse(settings.searchBoxes);
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleAndDb(
|
|
||||||
`[databaseUtil] Failed to parse searchBoxes for ${defaultSettings.activeDid}: ${error}`,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
// Reset to empty array on parse failure
|
|
||||||
settings.searchBoxes = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
@@ -326,3 +329,109 @@ export function mapColumnsToValues(
|
|||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug function to inspect raw settings data in the database
|
||||||
|
* This helps diagnose issues with data corruption or malformed JSON
|
||||||
|
* @param did Optional DID to inspect specific account settings
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
|
export async function debugSettingsData(did?: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const platform = PlatformServiceFactory.getInstance();
|
||||||
|
|
||||||
|
// Get all settings records
|
||||||
|
const allSettings = await platform.dbQuery("SELECT * FROM settings");
|
||||||
|
|
||||||
|
logConsoleAndDb(`[DEBUG] Total settings records: ${allSettings?.values?.length || 0}`, false);
|
||||||
|
|
||||||
|
if (allSettings?.values?.length) {
|
||||||
|
allSettings.values.forEach((row, index) => {
|
||||||
|
const settings = mapColumnsToValues(allSettings.columns, [row])[0];
|
||||||
|
logConsoleAndDb(`[DEBUG] Settings record ${index + 1}:`, false);
|
||||||
|
logConsoleAndDb(`[DEBUG] - ID: ${settings.id}`, false);
|
||||||
|
logConsoleAndDb(`[DEBUG] - accountDid: ${settings.accountDid}`, false);
|
||||||
|
logConsoleAndDb(`[DEBUG] - activeDid: ${settings.activeDid}`, false);
|
||||||
|
|
||||||
|
if (settings.searchBoxes) {
|
||||||
|
logConsoleAndDb(`[DEBUG] - searchBoxes type: ${typeof settings.searchBoxes}`, false);
|
||||||
|
logConsoleAndDb(`[DEBUG] - searchBoxes value: ${String(settings.searchBoxes)}`, false);
|
||||||
|
|
||||||
|
// Try to parse it
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(String(settings.searchBoxes));
|
||||||
|
logConsoleAndDb(`[DEBUG] - searchBoxes parsed successfully: ${JSON.stringify(parsed)}`, false);
|
||||||
|
} catch (parseError) {
|
||||||
|
logConsoleAndDb(`[DEBUG] - searchBoxes parse error: ${parseError}`, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logConsoleAndDb(`[DEBUG] - Full record: ${JSON.stringify(settings, null, 2)}`, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If specific DID provided, also check accounts table
|
||||||
|
if (did) {
|
||||||
|
const account = await platform.dbQuery("SELECT * FROM accounts WHERE did = ?", [did]);
|
||||||
|
logConsoleAndDb(`[DEBUG] Account for ${did}: ${JSON.stringify(account, null, 2)}`, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb(`[DEBUG] Error inspecting settings data: ${error}`, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform-agnostic JSON parsing utility
|
||||||
|
* Handles different SQLite implementations:
|
||||||
|
* - Web SQLite (wa-sqlite/absurd-sql): Auto-parses JSON strings to objects
|
||||||
|
* - Capacitor SQLite: Returns raw strings that need manual parsing
|
||||||
|
*
|
||||||
|
* @param value The value to parse (could be string or already parsed object)
|
||||||
|
* @param defaultValue Default value if parsing fails
|
||||||
|
* @returns Parsed object or default value
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
|
export function parseJsonField<T>(
|
||||||
|
value: unknown,
|
||||||
|
defaultValue: T
|
||||||
|
): T {
|
||||||
|
try {
|
||||||
|
// If already an object (web SQLite auto-parsed), return as-is
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[DEBUG] JSON field is already an object (auto-parsed by web SQLite), skipping parse`,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
return value as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a string (Capacitor SQLite or fallback), parse it
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[DEBUG] JSON field is a string, parsing JSON (Capacitor SQLite or web fallback)`,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
return JSON.parse(value) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's null/undefined, return default
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unexpected type, log and return default
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[DEBUG] JSON field has unexpected type: ${typeof value}, returning default`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[databaseUtil] Failed to parse JSON field: ${error}`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export default class SearchAreaView extends Vue {
|
|||||||
if (USE_DEXIE_DB) {
|
if (USE_DEXIE_DB) {
|
||||||
await db.open();
|
await db.open();
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
searchBoxes: [newSearchBox],
|
searchBoxes: searchBoxes as any, // Type assertion for Dexie compatibility
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.searchBox = newSearchBox;
|
this.searchBox = newSearchBox;
|
||||||
@@ -269,7 +269,7 @@ export default class SearchAreaView extends Vue {
|
|||||||
if (USE_DEXIE_DB) {
|
if (USE_DEXIE_DB) {
|
||||||
await db.open();
|
await db.open();
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
searchBoxes: [],
|
searchBoxes: "[]" as any, // Type assertion for Dexie compatibility
|
||||||
filterFeedByNearby: false,
|
filterFeedByNearby: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user