Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/class-solid/src/components/Experiment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
duplicateExperiment,
modifyExperiment,
} from "~/lib/store";
import { deepCopy } from "~/lib/utils";
import { ExperimentConfigForm } from "./ExperimentConfigForm";
import { ObservationsList } from "./ObservationsList";
import { PermutationsList } from "./PermutationsList";
Expand Down Expand Up @@ -63,7 +64,7 @@ export function AddExperimentDialog(props: {
return {
preset: "Default",
reference: {
...structuredClone(defaultPreset.config),
...deepCopy(defaultPreset.config),
name: `My experiment ${props.nextIndex}`,
},
permutations: [],
Expand Down
3 changes: 2 additions & 1 deletion apps/class-solid/src/components/PermutationsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
setPermutationConfigInExperiment,
swapPermutationAndReferenceConfiguration,
} from "~/lib/store";
import { deepCopy } from "~/lib/utils";
import {
MdiCakeVariantOutline,
MdiCog,
Expand Down Expand Up @@ -69,7 +70,7 @@ function AddPermutationButton(props: {
const [open, setOpen] = createSignal(false);

const initialPermutationConfig = createMemo(() => {
const config = structuredClone(unwrap(props.experiment.config.reference));
const config = deepCopy(unwrap(props.experiment.config.reference));
config.name = `${props.experiment.config.permutations.length + 1}`;
config.description = "";
return config;
Expand Down
11 changes: 6 additions & 5 deletions apps/class-solid/src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { parseExperimentConfig } from "./experiment_config";
import type { ExperimentConfig } from "./experiment_config";
import { findPresetByName } from "./presets";
import { runClass } from "./runner";
import { deepCopy } from "./utils";

interface ExperimentOutput {
reference?: ClassOutput;
Expand Down Expand Up @@ -112,7 +113,7 @@ export async function uploadExperiment(rawData: unknown) {
}

export function duplicateExperiment(id: number) {
const config = structuredClone(findExperiment(id).config);
const config = deepCopy(findExperiment(id).config);
config.reference.name = `Copy of ${config.reference.name}`;
const newExperiment: Experiment = {
config: config,
Expand Down Expand Up @@ -191,7 +192,7 @@ export function promotePermutationToExperiment(
const exp = findExperiment(experimentIndex);
const perm = exp.config.permutations[permutationIndex];

const newConfig = structuredClone(perm);
const newConfig = deepCopy(perm);
addExperiment(newConfig);
// TODO should permutation be removed from original experiment?
}
Expand All @@ -201,7 +202,7 @@ export function duplicatePermutation(
permutationIndex: number,
) {
const exp = findExperiment(experimentIndex);
const perm = structuredClone(exp.config.permutations[permutationIndex]);
const perm = deepCopy(exp.config.permutations[permutationIndex]);
perm.name = `Copy of ${perm.name}`;
setPermutationConfigInExperiment(experimentIndex, -1, perm);
runExperiment(experimentIndex);
Expand All @@ -212,9 +213,9 @@ export function swapPermutationAndReferenceConfiguration(
permutationIndex: number,
) {
const exp = findExperiment(experimentIndex);
const refConfig = structuredClone(exp.config.reference);
const refConfig = deepCopy(exp.config.reference);
const perm = exp.config.permutations[permutationIndex];
const permConfig = structuredClone(perm);
const permConfig = deepCopy(perm);

setExperiments(experimentIndex, "config", "reference", permConfig);
setExperiments(
Expand Down
21 changes: 21 additions & 0 deletions apps/class-solid/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,24 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

/**
* Creates a deep copy of the given object by serializing it to JSON and then
* deserializing it back.
*
* @typeParam T - The type of the object to be copied.
* @param obj - The object to create a deep copy of.
* @returns A new object that is a deep copy of the input object.
*
* @remarks
* This function uses `JSON.stringify` and `JSON.parse` to perform the deep
* copy. It works well for plain objects and arrays but may not handle objects
* with methods, circular references, or special types like `Date`, `Map`,
* `Set`, etc.
*
* Unlike structuredClone, this method does not preserve references. This is to
* avoid uncaught reference errors.
*/
export function deepCopy<T>(obj: T){
return JSON.parse(JSON.stringify(obj)) as T
}
2 changes: 1 addition & 1 deletion packages/class/src/config_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function pruneConfig(
reference: Config,
preset?: Config,
): PartialConfig {
let config = structuredClone(permutation);
let config = JSON.parse(JSON.stringify(permutation)) as Config;
let config2 = reference;
if (preset) {
config = pruneConfig(permutation, reference) as Config;
Expand Down
5 changes: 3 additions & 2 deletions packages/form/src/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
type Item,
type SchemaOfProperty,
buildValidate,
deepCopy,
isBase,
isBooleanChoices,
isGroup,
Expand Down Expand Up @@ -123,7 +124,7 @@ function createFormStore(
schema: JSONSchemaType<GenericConfig>;
}>({
// Copy props.values as initial form values
values: structuredClone(unwrap(initialValues)),
values: deepCopy(unwrap(initialValues)),
errors: [],
schema: schema(),
});
Expand All @@ -138,7 +139,7 @@ function createFormStore(
setStore("errors", errors);
},
reset: () => {
setStore("values", structuredClone(initialValues));
setStore("values", deepCopy(unwrap(initialValues)));
setStore("errors", []);
},
get errors() {
Expand Down
6 changes: 3 additions & 3 deletions packages/form/src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "node:assert";
import { describe, test } from "node:test";
import type { JSONSchemaType } from "ajv/dist/2020";
import { type Item, overwriteDefaultsInJsonSchema, schema2tree } from "./utils";
import { type Item, deepCopy, overwriteDefaultsInJsonSchema, schema2tree } from "./utils";

type Config = {
s1: string;
Expand Down Expand Up @@ -274,11 +274,11 @@ describe("schema2tree", () => {

describe("overwriteDefaultsInJsonSchema", () => {
test("given new default for s1 should return schema with given default", () => {
const schema = structuredClone(jsonSchemaOfConfig);
const schema = deepCopy(jsonSchemaOfConfig);

const result = overwriteDefaultsInJsonSchema(schema, defaults);

const expected = structuredClone(jsonSchemaOfConfig);
const expected = deepCopy(jsonSchemaOfConfig);
expected.properties.s1.default = "string2";

assert.deepEqual(result, expected);
Expand Down
21 changes: 21 additions & 0 deletions packages/form/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,24 @@ export function schema2tree<C>(schema: JSONSchemaType<C>): Item[] {
}
return tree;
}

/**
* Creates a deep copy of the given object by serializing it to JSON and then
* deserializing it back.
*
* @typeParam T - The type of the object to be copied.
* @param obj - The object to create a deep copy of.
* @returns A new object that is a deep copy of the input object.
*
* @remarks
* This function uses `JSON.stringify` and `JSON.parse` to perform the deep
* copy. It works well for plain objects and arrays but may not handle objects
* with methods, circular references, or special types like `Date`, `Map`,
* `Set`, etc.
*
* Unlike structuredClone, this method does not preserve references. This is to
* avoid uncaught reference errors.
*/
export function deepCopy<T>(obj: T){
return JSON.parse(JSON.stringify(obj)) as T
}
Loading