Bin
2025-12-17 05a69820e0c402b0b33c063d3b922f0a0571cbbb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import camelCase from "lodash/camelCase";
 
export const formDataToJPO = (formData: FormData) => {
  if (formData instanceof FormData) {
    const entries = formData.entries();
 
    return Array.from(entries).reduce((res, [key, value]) => {
      return { ...res, [key]: value };
    }, {});
  }
 
  return formData;
};
 
/**
 * Hydrate JSON values that are large integers to strings.
 */
export const jsonReviverWithBigInt = (_key: any, value: any, context?: any) => {
  if (typeof value === "number" && context?.source !== undefined && Math.abs(value) > Number.MAX_SAFE_INTEGER) {
    // If the number would overflow the JS number precision, retain it to a string
    // from the original source string.
    // Leaving as a string and not a BigInt to avoid issues with JSON.stringify or other cases downstream.
    return context.source;
  }
  return value;
};
/**
 * Parse a JSON string and convert big integers to strings.
 * We convert only big integers that are still integers within the JSON string
 * to avoid JS number precision issues when displaying them in the UI.
 * This is a workaround for the fact that JSON.parse does not directly support big integers and will
 * immediately convert them to numbers (losing precision).
 *
 * ex. { "id": 12345678901234567890 } => { "id": "12345678901234567890" }
 *     { "id": -12345678901234567890 } => { "id": "-12345678901234567890" }
 *     { "meta": { "id": 12345678901234567890 } } => { "meta": { "id": "12345678901234567890" } }
 *     { "meta": { "id": -12345678901234567890 } } => { "meta": { "id": "-12345678901234567890" } }
 **/
export const parseJson = <T = any>(jsonString: string): T => {
  return JSON.parse(jsonString, jsonReviverWithBigInt) as T;
};
 
export const objectToMap = <T extends Record<string, any>>(object: T) => {
  return new Map(Object.entries(object ?? {}));
};
 
export const mapToObject = <T extends Map<any, any>>(map: T) => {
  return Object.fromEntries(map);
};
 
export const filename = (string: string) => {
  if (string) {
    return (
      string
        .split("/")
        .slice(-1)[0]
        .match(/([^?]+)/g)?.[0] ?? string
    );
  }
};
 
export const isEmptyString = (value: any) => {
  return typeof value === "string" && value.trim().length === 0;
};
 
export const isEmptyObject = (value: any) => {
  return (typeof value === "object" && !value) || Object.keys(value).length === 0;
};
 
export const isEmptyArray = (value: any) => {
  return Array.isArray(value) && value.length === 0;
};
 
export const isEmpty = (value: any) => {
  return isEmptyString(value) || isEmptyObject(value) || isEmptyArray(value);
};
 
type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
  ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
  : Lowercase<S>;
 
type ObjectToCamel<T> = {
  [K in keyof T as CamelCase<string & K>]: T[K] extends Record<string, any> ? KeysToCamelCase<T[K]> : T[K];
};
 
type KeysToCamelCase<T> = {
  [K in keyof T as CamelCase<string & K>]: T[K] extends Array<any>
    ? KeysToCamelCase<T[K][number]>[]
    : ObjectToCamel<T[K]>;
};
 
export const camelizeKeys = <T extends AnyObject>(source: T): KeysToCamelCase<T> => {
  type Pair = [string, unknown];
 
  const split = Object.entries(source);
 
  const pairs: Pair[] = split.map<Pair>(([key, value]) => {
    if (Object.prototype.toString.call(value) === "[object Object]") {
      return [camelCase(key), camelizeKeys(value as T)];
    }
 
    return [camelCase(key), value];
  });
 
  return Object.fromEntries(pairs) as KeysToCamelCase<T>;
};
 
export const hasProperties = (obj: AnyObject, properties: string[], all?: boolean) => {
  if (!isDefined(obj)) return false;
 
  return all
    ? properties.reduce((res, prop) => {
        return res && Object.prototype.hasOwnProperty.call(obj, prop);
      }, true)
    : properties.findIndex((prop) => {
        return Object.prototype.hasOwnProperty.call(obj, prop);
      }) >= 0;
};
 
export const objectClean = <T extends AnyObject>(source: T) => {
  const cleanObject: [keyof T, unknown][] = Object.entries(source).reduce<[keyof T, unknown][]>((res, [key, value]) => {
    const valueIsDefined = isDefined(value) && !isEmptyString(value);
 
    if (!valueIsDefined) {
      return res;
    }
 
    if (Object.prototype.toString.call(value) === "[object Object]") {
      return [...res, [key, objectClean(value as AnyObject)]];
    }
    return [...res, [key, value]];
  }, []);
 
  return Object.fromEntries(cleanObject) as T;
};
 
export const clamp = (value: number, min: number, max: number) => {
  return Math.max(min, Math.min(value, max));
};
 
export const absoluteURL = (path = "") => {
  if (path.match(/^https?/) || path.match(/^\/\//)) {
    return path;
  }
  return [APP_SETTINGS.hostname.replace(/([/]+)$/, ""), path.replace(/^([/]+)/, "")].join("/");
};
 
export const isDefined = <T>(value?: T): value is NonNullable<T> => {
  return value !== null && value !== undefined;
};