From f8a06dc0a5c4639a027992d56734ae121560de08 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 21 Feb 2025 05:03:11 -0700 Subject: [PATCH 1/4] Add integer handling that is separate from number --- client/src/components/DynamicJsonForm.tsx | 30 +++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index ff26118f1..b3de22aed 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -103,20 +103,30 @@ const DynamicJsonForm = ({ switch (propSchema.type) { case "string": + return ( + handleFieldChange(path, e.target.value)} + placeholder={propSchema.description} + /> + ); case "number": + return ( + handleFieldChange(path, Number(e.target.value))} + placeholder={propSchema.description} + /> + ); case "integer": return ( - handleFieldChange( - path, - propSchema.type === "string" - ? e.target.value - : Number(e.target.value), - ) - } + type="number" + step="1" + value={(currentValue as number) ?? ""} + onChange={(e) => handleFieldChange(path, parseInt(e.target.value, 10) || 0)} placeholder={propSchema.description} /> ); From ccba97d363969122778b8a1af33a7de833d86fba Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 21 Feb 2025 07:45:42 -0700 Subject: [PATCH 2/4] Handle required fields and defaults better --- client/src/components/DynamicJsonForm.tsx | 94 ++++++++++++++++------- 1 file changed, 68 insertions(+), 26 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index b3de22aed..7ed3b3dfd 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -9,12 +9,15 @@ export type JsonValue = | number | boolean | null + | undefined | JsonValue[] | { [key: string]: JsonValue }; export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object"; + type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null"; description?: string; + required?: boolean; + default?: JsonValue; properties?: Record; items?: JsonSchemaType; }; @@ -44,28 +47,36 @@ const DynamicJsonForm = ({ const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); - const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue => { + const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue | undefined => { + // Return schema default if provided + if ('default' in propSchema) { + return propSchema.default; + } + + if (!propSchema.required) { + return undefined; + } + switch (propSchema.type) { - case "string": - return ""; + case "string": return ""; case "number": - case "integer": - return 0; - case "boolean": - return false; - case "array": - return []; + case "integer": return 0; + case "boolean": return false; + case "array": return []; case "object": { + if (!propSchema.properties) return {}; const obj: JsonObject = {}; - if (propSchema.properties) { - Object.entries(propSchema.properties).forEach(([key, prop]) => { - obj[key] = generateDefaultValue(prop); + Object.entries(propSchema.properties) + .filter(([, prop]) => prop.required) + .forEach(([key, prop]) => { + const value = generateDefaultValue(prop); + if (value !== undefined) { + obj[key] = value; + } }); - } return obj; } - default: - return null; + default: return undefined; } }; @@ -107,17 +118,34 @@ const DynamicJsonForm = ({ handleFieldChange(path, e.target.value)} + onChange={(e) => { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + handleFieldChange(path, val || null); + } + }} placeholder={propSchema.description} + required={propSchema.required} /> ); case "number": return ( handleFieldChange(path, Number(e.target.value))} + value={(currentValue as number)?.toString() ?? ""} + onChange={(e) => { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + const num = Number(val); + handleFieldChange(path, isNaN(num) ? null : num); + } + }} placeholder={propSchema.description} + required={propSchema.required} /> ); case "integer": @@ -125,9 +153,18 @@ const DynamicJsonForm = ({ handleFieldChange(path, parseInt(e.target.value, 10) || 0)} + value={(currentValue as number)?.toString() ?? ""} + onChange={(e) => { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + const num = parseInt(val, 10); + handleFieldChange(path, isNaN(num) ? null : num); + } + }} placeholder={propSchema.description} + required={propSchema.required} /> ); case "boolean": @@ -135,8 +172,15 @@ const DynamicJsonForm = ({ handleFieldChange(path, e.target.checked)} + onChange={(e) => { + if (!e.target.checked && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + handleFieldChange(path, e.target.checked); + } + }} className="w-4 h-4" + required={propSchema.required} /> ); case "object": @@ -197,10 +241,8 @@ const DynamicJsonForm = ({ variant="outline" size="sm" onClick={() => { - handleFieldChange(path, [ - ...arrayValue, - generateDefaultValue(propSchema.items as JsonSchemaType), - ]); + const defaultValue = generateDefaultValue(propSchema.items as JsonSchemaType); + handleFieldChange(path, [...arrayValue, defaultValue ?? null]); }} title={ propSchema.items?.description From 1c935b6df0c2cc27d519ec040d0e23cd4f305777 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 21 Feb 2025 08:10:42 -0700 Subject: [PATCH 3/4] Revert some changes related to defaults --- client/src/components/DynamicJsonForm.tsx | 30 ++++++++++------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 7ed3b3dfd..3f0a8b828 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -93,11 +93,7 @@ const DynamicJsonForm = ({ // Render as JSON editor when max depth is reached return ( { try { const parsed = JSON.parse(newValue); @@ -120,10 +116,10 @@ const DynamicJsonForm = ({ value={(currentValue as string) ?? ""} onChange={(e) => { const val = e.target.value; - if (!val && !propSchema.required) { + if (!val) { handleFieldChange(path, undefined); } else { - handleFieldChange(path, val || null); + handleFieldChange(path, val); } }} placeholder={propSchema.description} @@ -137,11 +133,13 @@ const DynamicJsonForm = ({ value={(currentValue as number)?.toString() ?? ""} onChange={(e) => { const val = e.target.value; - if (!val && !propSchema.required) { + if (!val) { handleFieldChange(path, undefined); } else { const num = Number(val); - handleFieldChange(path, isNaN(num) ? null : num); + if (!isNaN(num)) { + handleFieldChange(path, num); + } } }} placeholder={propSchema.description} @@ -156,11 +154,13 @@ const DynamicJsonForm = ({ value={(currentValue as number)?.toString() ?? ""} onChange={(e) => { const val = e.target.value; - if (!val && !propSchema.required) { + if (!val) { handleFieldChange(path, undefined); } else { - const num = parseInt(val, 10); - handleFieldChange(path, isNaN(num) ? null : num); + const num = Number(val); + if (!isNaN(num)) { + handleFieldChange(path, num); + } } }} placeholder={propSchema.description} @@ -173,11 +173,7 @@ const DynamicJsonForm = ({ type="checkbox" checked={(currentValue as boolean) ?? false} onChange={(e) => { - if (!e.target.checked && !propSchema.required) { - handleFieldChange(path, undefined); - } else { - handleFieldChange(path, e.target.checked); - } + handleFieldChange(path, e.target.checked); }} className="w-4 h-4" required={propSchema.required} From b9c033b6b963f5e559519bbc5d44a9103c4764f8 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 21 Feb 2025 08:53:27 -0700 Subject: [PATCH 4/4] Fix formatting --- client/src/components/DynamicJsonForm.tsx | 47 +++++++++++++++++------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 3f0a8b828..e9c07ff08 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -14,7 +14,14 @@ export type JsonValue = | { [key: string]: JsonValue }; export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null"; + type: + | "string" + | "number" + | "integer" + | "boolean" + | "array" + | "object" + | "null"; description?: string; required?: boolean; default?: JsonValue; @@ -47,22 +54,28 @@ const DynamicJsonForm = ({ const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); - const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue | undefined => { + const generateDefaultValue = ( + propSchema: JsonSchemaType, + ): JsonValue | undefined => { // Return schema default if provided - if ('default' in propSchema) { + if ("default" in propSchema) { return propSchema.default; } - + if (!propSchema.required) { return undefined; } switch (propSchema.type) { - case "string": return ""; + case "string": + return ""; case "number": - case "integer": return 0; - case "boolean": return false; - case "array": return []; + case "integer": + return 0; + case "boolean": + return false; + case "array": + return []; case "object": { if (!propSchema.properties) return {}; const obj: JsonObject = {}; @@ -76,7 +89,8 @@ const DynamicJsonForm = ({ }); return obj; } - default: return undefined; + default: + return undefined; } }; @@ -93,7 +107,11 @@ const DynamicJsonForm = ({ // Render as JSON editor when max depth is reached return ( { try { const parsed = JSON.parse(newValue); @@ -237,8 +255,13 @@ const DynamicJsonForm = ({ variant="outline" size="sm" onClick={() => { - const defaultValue = generateDefaultValue(propSchema.items as JsonSchemaType); - handleFieldChange(path, [...arrayValue, defaultValue ?? null]); + const defaultValue = generateDefaultValue( + propSchema.items as JsonSchemaType, + ); + handleFieldChange(path, [ + ...arrayValue, + defaultValue ?? null, + ]); }} title={ propSchema.items?.description