Files
2026-07-12 10:17:17 -04:00

2.0 KiB

PocketBase JSON Field Guard

The problem: When loading data from PocketBase collections, nested JSON fields (like services array on quotes) may come back as either a parsed array OR a JSON string — depending on how the field type is defined in the PocketBase collection schema.

Where this bites: The Daily Briefing scans q.services to count unaddressed customer decisions. If services is a JSON string, services.filter(...) iterates over individual characters instead of objects. !s.customerDecision is always true for a character like { — so every quote shows as "needing decisions" even when all services are approved/declined.

The three-step guard — apply EVERYWHERE you read nested JSON from PocketBase:

// Step 1: Default to empty array
let services = q.services || [];

// Step 2: Parse if it's a JSON string
if (typeof services === 'string') {
    try { services = JSON.parse(services); } catch(e) { services = []; }
}

// Step 3: Ensure it's actually an array
if (!Array.isArray(services)) services = [];

// Now safe to use .filter(), .forEach(), etc.
const unaddressed = services.filter(s => !s.customerDecision || s.customerDecision === 'pending');

Why convertFromPB doesn't help: The convertFromPB() function in pocketbase.js (line 422) tries to JSON.parse() top-level string fields that start with { or [. But when PocketBase returns a field as an already-parsed array (the normal case for JSON-typed fields), convertFromPB doesn't recurse into it. And when it returns as a string (text-typed fields), convertFromPB does parse it — but inconsistently. The guard handles both cases.

Files where this guard is applied:

  • dashboard.jsgenerateDailyBriefing() quote analysis (two locations)
  • quote-tab-manager.js — any function reading saved quote data

When adding new code that reads PocketBase-sourced nested JSON: always add this guard. It costs nothing when the field is already an array, and prevents silent bugs when it's a string.