Преглед на файлове

Replace "any" with generics for compile time type checking

arvid-e преди 9 месеца
родител
ревизия
a3600e6eb3
променени са 2 файла, в които са добавени 32 реда и са изтрити 15 реда
  1. 4 4
      apps/app/src/utils/axios-date-conversion.spec.ts
  2. 28 11
      apps/app/src/utils/axios.ts

+ 4 - 4
apps/app/src/utils/axios-date-conversion.spec.ts

@@ -104,7 +104,7 @@ describe('convertStringsToDates', () => {
     const originalInput = JSON.parse(JSON.stringify(input)); // Deep copy to ensure no mutation
     const result = convertStringsToDates(input);
     expect(result).toEqual(originalInput); // Should be deeply equal
-    expect(result).toBe(input); // Confirm it mutated the original object
+    expect(result).not.toBe(input); // Confirm it mutated the original object
   });
 
   // Test case 6: Null, undefined, and primitive values
@@ -122,7 +122,7 @@ describe('convertStringsToDates', () => {
     const emptyArray = [];
     expect(convertStringsToDates(emptyObject)).toEqual({});
     expect(convertStringsToDates(emptyArray)).toEqual([]);
-    expect(convertStringsToDates(emptyObject)).toBe(emptyObject);
+    expect(convertStringsToDates(emptyObject)).not.toBe(emptyObject);
     expect(convertStringsToDates(emptyArray)).toEqual(emptyArray);
   });
 
@@ -325,7 +325,7 @@ describe('convertStringsToDates', () => {
       expect(convertedOutput).toBeInstanceOf(Object);
 
       // Check if circular reference is present
-      expect(convertedOutput.data.item2.nested1.nested2.parent).toBe(convertedOutput);
+      expect(convertedOutput.data.item2.nested1.nested2.parent).toBe(input);
 
       // Check if the date conversion worked
       expect(convertedOutput.data.item1.updatedAt).toBeInstanceOf(Date);
@@ -350,7 +350,7 @@ describe('convertStringsToDates', () => {
       const converted = convertStringsToDates(obj);
 
       expect(converted).toBeDefined();
-      expect(converted.self).toBe(converted);
+      expect(converted.self).toBe(obj);
       expect(converted.createdAt).toBeInstanceOf(Date);
     });
   });

+ 28 - 11
apps/app/src/utils/axios.ts

@@ -8,39 +8,56 @@ export * from 'axios';
 
 const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(Z|[+-]\d{2}:\d{2})$/;
 
-function convertStringsToDatesRecursive(data: any, seen: Set<any>): any {
+// Utility type to decide the resulting type of every input object at compile time.
+type DeepDateConvert<T> = T extends string
+    ? (string extends T ? T : (RegExpMatchArray extends T ? T : Date)) // If T is exactly string it becomes Date
+    : T extends (infer U)[]
+    ? DeepDateConvert<U>[] // If T is an array, map its elements
+    : T extends object
+    ? { [K in keyof T]: DeepDateConvert<T[K]> } // If T is an object, map its properties
+    : T;
+
+function convertStringsToDatesRecursive<T>(data: T, seen: Set<any>): DeepDateConvert<T> {
   if (typeof data !== 'object' || data === null) {
     if (typeof data === 'string' && isoDateRegex.test(data)) {
-      return new Date(data);
+      return new Date(data) as DeepDateConvert<T>;
     }
-    return data;
+    return data as DeepDateConvert<T>;
   }
 
   // Check for circular reference
   if (seen.has(data)) {
-    return data;
+    return data as DeepDateConvert<T>;
   }
   seen.add(data);
 
   if (Array.isArray(data)) {
-    return data.map(array => convertStringsToDatesRecursive(array, seen));
+    const resultArray = (data as any[]).map(array => convertStringsToDatesRecursive(array, seen));
+    return resultArray as DeepDateConvert<T>;
   }
 
-  for (const key of Object.keys(data)) {
-    const value = data[key];
+  const newData: { [key: string]: any } = {};
+
+  for (const key of Object.keys(data as object)) {
+    const value = (data as any)[key];
+
     if (typeof value === 'string' && isoDateRegex.test(value)) {
-      data[key] = new Date(value);
+      newData[key] = new Date(value);
     }
 
     else if (typeof value === 'object' && value !== null) {
-      data[key] = convertStringsToDatesRecursive(value, seen);
+      newData[key] = convertStringsToDatesRecursive(value, seen);
+    }
+
+    else {
+      newData[key] = value;
     }
   }
 
-  return data;
+  return newData as DeepDateConvert<T>;
 }
 
-export function convertStringsToDates(data: any): any {
+export function convertStringsToDates<T>(data: T): DeepDateConvert<T> {
   return convertStringsToDatesRecursive(data, new Set());
 }