diff --git a/src/components/SchemaEditor/SchemaVisualEditor.tsx b/src/components/SchemaEditor/SchemaVisualEditor.tsx index 51d0d77..36a069a 100644 --- a/src/components/SchemaEditor/SchemaVisualEditor.tsx +++ b/src/components/SchemaEditor/SchemaVisualEditor.tsx @@ -4,6 +4,7 @@ import { createFieldSchema, updateObjectProperty, updatePropertyRequired, + renameObjectProperty, } from "../../lib/schemaEditor.ts"; import type { JSONSchema, NewField } from "../../types/jsonSchema.ts"; import { asObjectSchema, isBooleanSchema } from "../../types/jsonSchema.ts"; @@ -50,12 +51,17 @@ const SchemaVisualEditor: FC = ({ // Create a field schema based on the updated field data const fieldSchema = createFieldSchema(updatedField); - // Update the field in the schema - let newSchema = updateObjectProperty( - asObjectSchema(schema), - updatedField.name, - fieldSchema, - ); + let newSchema = asObjectSchema(schema); + + // If name changed, rename the property while preserving order + if (name !== updatedField.name) { + newSchema = renameObjectProperty(newSchema, name, updatedField.name); + // Update the field schema after rename + newSchema = updateObjectProperty(newSchema, updatedField.name, fieldSchema); + } else { + // Name didn't change, just update the schema + newSchema = updateObjectProperty(newSchema, name, fieldSchema); + } // Update required status newSchema = updatePropertyRequired( @@ -64,29 +70,6 @@ const SchemaVisualEditor: FC = ({ updatedField.required || false, ); - // If name changed, we need to remove the old field - if (name !== updatedField.name) { - const { properties, ...rest } = newSchema; - const { [name]: _, ...remainingProps } = properties || {}; - - newSchema = { - ...rest, - properties: remainingProps, - }; - - // Re-add the field with the new name - newSchema = updateObjectProperty( - newSchema, - updatedField.name, - fieldSchema, - ); - - // Re-update required status if needed - if (updatedField.required) { - newSchema = updatePropertyRequired(newSchema, updatedField.name, true); - } - } - // Update the schema onChange(newSchema); }; diff --git a/src/lib/schemaEditor.ts b/src/lib/schemaEditor.ts index 3b9f1e0..14f7685 100644 --- a/src/lib/schemaEditor.ts +++ b/src/lib/schemaEditor.ts @@ -157,6 +157,40 @@ export function getArrayItemsSchema(schema: JSONSchema): JSONSchema | null { return schema.items || null; } +/** + * Renames a property while preserving order in the object schema + */ +export function renameObjectProperty( + schema: ObjectJSONSchema, + oldName: string, + newName: string, +): ObjectJSONSchema { + if (!isObjectSchema(schema) || !schema.properties) return schema; + + const newSchema = copySchema(schema); + const newProperties: Record = {}; + + // Iterate through properties in order, replacing old key with new key + for (const [key, value] of Object.entries(newSchema.properties)) { + if (key === oldName) { + newProperties[newName] = value; + } else { + newProperties[key] = value; + } + } + + newSchema.properties = newProperties; + + // Update required array if the field name changed + if (newSchema.required) { + newSchema.required = newSchema.required.map((field) => + field === oldName ? newName : field, + ); + } + + return newSchema; +} + /** * Checks if a schema has children */ diff --git a/test/lib/schemaEditor.test.ts b/test/lib/schemaEditor.test.ts new file mode 100644 index 0000000..38ac0a6 --- /dev/null +++ b/test/lib/schemaEditor.test.ts @@ -0,0 +1,23 @@ +import { describe, test } from "node:test"; +import assert from "node:assert"; +import { renameObjectProperty } from "../../src/lib/schemaEditor.ts"; + +describe("renameObjectProperty", () => { + test("preserves property order when renaming", () => { + const schema = { + type: "object" as const, + properties: { + firstName: { type: "string" as const }, + lastName: { type: "string" as const }, + email: { type: "string" as const }, + }, + required: ["firstName", "lastName", "email"], + }; + + const result = renameObjectProperty(schema, "lastName", "surname"); + + const keys = Object.keys(result.properties); + assert.deepStrictEqual(keys, ["firstName", "surname", "email"]); + assert.deepStrictEqual(result.required, ["firstName", "surname", "email"]); + }); +});