forked from trent_larson/crowd-funder-for-time-pwa
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:
@@ -135,6 +135,19 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
|
||||
result.columns,
|
||||
result.values,
|
||||
)[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(
|
||||
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
|
||||
);
|
||||
@@ -144,17 +157,7 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
|
||||
|
||||
// Handle searchBoxes parsing
|
||||
if (settings.searchBoxes) {
|
||||
try {
|
||||
// @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 = [];
|
||||
}
|
||||
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
|
||||
}
|
||||
|
||||
return settings;
|
||||
@@ -326,3 +329,109 @@ export function mapColumnsToValues(
|
||||
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) {
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
searchBoxes: [newSearchBox],
|
||||
searchBoxes: searchBoxes as any, // Type assertion for Dexie compatibility
|
||||
});
|
||||
}
|
||||
this.searchBox = newSearchBox;
|
||||
@@ -269,7 +269,7 @@ export default class SearchAreaView extends Vue {
|
||||
if (USE_DEXIE_DB) {
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
searchBoxes: [],
|
||||
searchBoxes: "[]" as any, // Type assertion for Dexie compatibility
|
||||
filterFeedByNearby: false,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user