lib(renderer): add safeJson helper; migrate 6 JSON.parse try/catch call sites

This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 22:54:20 -04:00
1 parent a07b42e493
commit be0ff5a041
8 files changed
+46 -48

No files matched your search

@@ -11,6 +11,7 @@ import {
} from "@phosphor-icons/react"
import type { AgentMessageRecord, AgentRecord, CommentRecord, DepartmentId } from "@shared/types"
import { getAgentInitials, formatMessageTime } from "../../lib/formatters"
import { safeJson } from "../../lib/safe-json"
import { DEPARTMENT_COLORS } from "@shared/constants"
const HUMAN_SENDER_ID = "__board__";
@@ -196,12 +197,7 @@ export function TaskCommentBubble({
}
function AttachmentList({ json }: { json: string }) {
let items: Array<{ type: string; id: string; title: string }> = []
try {
items = JSON.parse(json)
} catch {
return null
}
const items = safeJson<Array<{ type: string; id: string; title: string }>>(json, [])
if (!items.length) return null
return (
@@ -60,13 +60,10 @@ export function splitTags(value: string) {
return JSON.stringify(value.split(",").map((item) => item.trim()).filter(Boolean));
}
import { safeJson } from "../../lib/safe-json";
export function parseTags(value: string) {
try {
const tags = JSON.parse(value) as string[];
return tags.join(", ");
} catch {
return "";
}
return safeJson<string[]>(value, []).join(", ");
}
export function parseParticipantIds(value: string) {
@@ -79,12 +76,7 @@ export function parseParticipantIds(value: string) {
import type { AgentRecord } from "@shared/types";
export function formatParticipantIds(raw: string, agents: AgentRecord[]) {
try {
const ids = JSON.parse(raw) as string[];
return ids
.map((id) => agents.find((agent) => agent.id === id)?.name ?? id.slice(0, 8))
.join(", ");
} catch {
return "";
}
return safeJson<string[]>(raw, [])
.map((id) => agents.find((agent) => agent.id === id)?.name ?? id.slice(0, 8))
.join(", ");
}
@@ -1,6 +1,7 @@
import { Clock, Link as LinkIcon } from "@phosphor-icons/react"
import { platformConfig } from "../social/social-config"
import type { SocialPlatform } from "@shared/types"
import { safeJson } from "../../lib/safe-json"
const PLATFORM_CHAR_LIMITS: Record<string, number> = {
twitter: 280,
@@ -26,19 +27,16 @@ interface DraftPayload {
}
function parseDraft(payloadJson: string): DraftPayload | null {
try {
const parsed = JSON.parse(payloadJson)
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
return {
content: typeof parsed.content === "string" ? parsed.content : "",
mediaUrls: Array.isArray(parsed.mediaUrls) ? parsed.mediaUrls : [],
scheduledAt: typeof parsed.scheduledAt === "string" ? parsed.scheduledAt : null,
socialAccountId: typeof parsed.socialAccountId === "string" ? parsed.socialAccountId : "",
platform: typeof parsed.platform === "string" ? parsed.platform : "other",
}
}
} catch { /* invalid JSON */ }
return null
const parsed = safeJson<unknown>(payloadJson, null)
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null
const obj = parsed as Record<string, unknown>
return {
content: typeof obj.content === "string" ? obj.content : "",
mediaUrls: Array.isArray(obj.mediaUrls) ? (obj.mediaUrls as string[]) : [],
scheduledAt: typeof obj.scheduledAt === "string" ? obj.scheduledAt : null,
socialAccountId: typeof obj.socialAccountId === "string" ? obj.socialAccountId : "",
platform: typeof obj.platform === "string" ? obj.platform : "other",
}
}
function formatScheduledDate(isoDate: string): string {
@@ -1,18 +1,15 @@
import { useMemo } from "react"
import type { ApprovalRecord } from "@shared/types"
import { safeJson } from "../../../lib/safe-json"
export function useApprovalPayload(approval: ApprovalRecord): Record<string, unknown> | null {
return useMemo(() => {
if (!approval.payloadJson) return null
try {
const parsed = JSON.parse(approval.payloadJson)
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>
}
return null
} catch {
return null
const parsed = safeJson<unknown>(approval.payloadJson, null)
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>
}
return null
}, [approval.payloadJson])
}
@@ -2,6 +2,7 @@ import type { ApprovalRecord } from "@shared/types"
import {
Robot, CheckSquare, Play, ShieldCheck, Gear, FolderOpen, Key, Target, Briefcase, Buildings,
} from "@phosphor-icons/react"
import { safeJson } from "../../lib/safe-json"
export function describeActionError(error: unknown) {
return error instanceof Error ? error.message : "The action could not be completed."
@@ -9,10 +10,8 @@ export function describeActionError(error: unknown) {
export function parseApprovalPayloadJson(approval: ApprovalRecord): Record<string, unknown> | null {
if (!approval.payloadJson) return null
try {
const parsed = JSON.parse(approval.payloadJson)
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed as Record<string, unknown>
} catch { return null }
const parsed = safeJson<unknown>(approval.payloadJson, null)
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed as Record<string, unknown>
return null
}
@@ -9,6 +9,7 @@ import type { AgentRecord, ApprovalRecord } from "@shared/types";
import { useT } from "../../i18n";
import { getAgentInitials } from "../../lib/formatters";
import { unwrap } from "../../lib/desktop";
import { safeJson } from "../../lib/safe-json";
import { ActionButton, EmptyState, Panel, StatusPill } from "../ui";
import {
type HiringStage,
@@ -44,7 +45,8 @@ export function HiringPipeline({ agents, approvals, onRefresh, onNavigateToAgent
const approval = hireApprovals.find(a => {
if (a.relatedAgentId === agent.id) return true;
if (!a.payloadJson) return false;
try { return JSON.parse(a.payloadJson).agentId === agent.id; } catch { return false; }
const payload = safeJson<{ agentId?: string }>(a.payloadJson, {});
return payload.agentId === agent.id;
}) ?? null;
return {
agent,
@@ -4,6 +4,7 @@ import { DEPARTMENT_OPTIONS } from "@shared/constants";
import type { AgentRecord, BudgetPolicyRecord, ConnectorRecord, DepartmentId, WorkspaceRecord } from "@shared/types";
import { getConnectorExecutionReadinessIssue, isConnectorExecutionReady } from "@shared/connector-policy";
import { unwrap } from "../../lib/desktop";
import { safeJson } from "../../lib/safe-json";
import { buildBudgetPolicyInput, getBudgetPolicyAmountDollars } from "../../lib/budget-policy-input";
import { useAppStore } from "../../state/store";
import { useT } from "../../i18n";
@@ -43,8 +44,13 @@ export function AgentEditor({
const [reportsTo, setReportsTo] = useState(agent.reportsTo ?? "");
const [confirmingDelete, setConfirmingDelete] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const existingMeta = useMemo(() => { try { return JSON.parse(agent.metadataJson || "{}"); } catch { return {}; } }, [agent.metadataJson]);
const [thinkingEffort, setThinkingEffort] = useState<string>(existingMeta.thinkingEffort ?? "");
const existingMeta = useMemo(
() => safeJson<Record<string, unknown>>(agent.metadataJson, {}),
[agent.metadataJson],
);
const [thinkingEffort, setThinkingEffort] = useState<string>(
typeof existingMeta.thinkingEffort === "string" ? existingMeta.thinkingEffort : "",
);
const supportsEffort = connectorId === "claude_local" || connectorId === "codex_local";
const pendingApproval = agent.status === "pending_approval";
const executionReadyConnectors = useMemo(