@@ -12,9 +12,38 @@ function isEmptyArray(arr: unknown) {
1212
1313interface RemovalOptions {
1414 preserveEmptyArray ?: boolean ;
15+ preserveNullishArrays ?: boolean ;
1516 removeAllFalsy ?: boolean ;
1617}
1718
19+ // Remove objects that has undefined value or recursively contain undefined values
20+ // biome-ignore lint/suspicious/noExplicitAny: This method does its own type assertions.
21+ function removeUndefined ( obj : any ) : any {
22+ if ( obj === undefined ) {
23+ return undefined ;
24+ }
25+ // Preserve null
26+ if ( obj === null ) {
27+ return null ;
28+ }
29+ // Remove undefined in arrays
30+ if ( Array . isArray ( obj ) ) {
31+ return obj . map ( removeUndefined ) . filter ( item => item !== undefined ) ;
32+ }
33+ if ( typeof obj === 'object' ) {
34+ // biome-ignore lint/suspicious/noExplicitAny: We're just passing around the object values
35+ const cleaned : Record < string , any > = { } ;
36+ Object . entries ( obj ) . forEach ( ( [ key , value ] ) => {
37+ const cleanedValue = removeUndefined ( value ) ;
38+ if ( cleanedValue !== undefined ) {
39+ cleaned [ key ] = cleanedValue ;
40+ }
41+ } ) ;
42+ return cleaned ;
43+ }
44+ return obj ;
45+ }
46+
1847// Modified from here: https://stackoverflow.com/a/43781499
1948// biome-ignore lint/suspicious/noExplicitAny: This method does its own type assertions.
2049function stripEmptyObjects ( obj : any , options : RemovalOptions = { } ) {
@@ -69,8 +98,8 @@ function stripEmptyObjects(obj: any, options: RemovalOptions = {}) {
6998 } else {
7099 cleanObj [ idx ] = value ;
71100 }
72- } else if ( value === null ) {
73- // Null entries within an array should be removed.
101+ } else if ( value === null && ( options . removeAllFalsy || ! options . preserveNullishArrays ) ) {
102+ // Null entries within an array should be removed by default, unless explicitly preserved
74103 delete cleanObj [ idx ] ;
75104 }
76105 } ) ;
@@ -85,12 +114,12 @@ export default function removeUndefinedObjects<T>(obj?: T, options?: RemovalOpti
85114 return undefined ;
86115 }
87116
88- // JSON.stringify removes undefined values. Though `[undefined]` will be converted with this to
89- // `[null]`, we'll clean that up next.
90- // eslint-disable-next-line try-catch-failsafe/json-parse
91- let withoutUndefined = JSON . parse ( JSON . stringify ( obj ) ) ;
117+ // If array nulls are preserved, use the custom removeUndefined function so that
118+ // undefined values in arrays aren't converted to nulls, which stringify does
119+ // If we're not preserving array nulls (default behavior), it doesn't matter that the undefined array values are converted to nulls
120+ let withoutUndefined = options ?. preserveNullishArrays ? removeUndefined ( obj ) : JSON . parse ( JSON . stringify ( obj ) ) ;
92121
93- // Then we recursively remove all empty objects and nullish arrays.
122+ // Then we recursively remove all empty objects and nullish arrays
94123 withoutUndefined = stripEmptyObjects ( withoutUndefined , options ) ;
95124
96125 // If the only thing that's leftover is an empty object or empty array then return nothing.
0 commit comments