#map undefined to null

34 messages · Page 1 of 1 (latest)

shrewd wigeon
#

Hi, I have an unfortunate need to have the same type using nulls and using undefined, but, of course, not both.
I have a system ( that some one here helped me build. Thank you. ) that converts nulls into undefineds. Today I'm trying to reverse it and I find that it creates

type X = {
  x?: string | null
}

when what I need is

type X = {
  x: string | null
}

the pertinent part of the code is

export type RecursivelyReplaceUndefinedWithNull<T> = T extends undefined
  ? null

then it recurses through the properties.
I feel like it could be done with property maps but I'm not sure how.
thanks

remote urchin
#
type X = {
  x?: string | null;
};

type Y = {
  [K in keyof X]-?: X[K];
};
#

!ts Y

merry bearBOT
#
type Y = {
    x: string | null;
} /* 5:6 */```
remote urchin
#

its removing making the optional property required to be exact, not only removing undefined

#

wait Required<X> is easier

shrewd wigeon
#

so to put that in the ternary above I would do

export type RecursivelyReplaceUndefinedWithNull<T> = T extends undefined
  ? [K in keyof T]-?: T[K];
  :

is that right?

remote urchin
#

thats not recursive yet

shrewd wigeon
#

yes, I'm leaving that part out for brevity. but the place that it does the replacement is above

#
export type RecursivelyReplaceUndefinedWithNull<T> = T extends undefined
  ? [K in keyof T]-?: T[K]
  : T extends (infer U)[]
  ? RecursivelyReplaceUndefinedWithNull<U>[]
  : T extends Record<string, unknown>
  ? { [K in keyof T]: RecursivelyReplaceUndefinedWithNull<T[K]> }
  : T;
#

yea, it ... doesn't like that

remote urchin
#

[K in keyof T]-?: T[K] needs to be in { }

shrewd wigeon
#

yep, likes that better 🙂 thanks. Then I also have a function


export function undefinedsToNull<T>(
  obj: T,
): RecursivelyReplaceUndefinedWithNull<T> {
  if (obj === null || obj === undefined) {
    return null as any;
  }

  if ((obj as any).constructor.name === 'Object' || Array.isArray(obj)) {
    for (const key in obj) {
      obj[key] = undefinedsToNull(obj[key]) as any;
    }
  }
  return obj as any;
}

but I presume that will be ok as is?

remote urchin
#

should it also convert x: string | undefined; ?

shrewd wigeon
#

so what I"m getting after doing
undefinedsToNull(user) are props like this
name?: string | undefined;

#

oh, is that why?

remote urchin
#
export type RecursivelyReplaceUndefinedWithNull<T> = T extends undefined
  ? Exclude<T, undefined>
  : T extends (infer U)[]
  ? RecursivelyReplaceUndefinedWithNull<U>[]
  : T extends Record<string, unknown>
  ? { [K in keyof T]-?: RecursivelyReplaceUndefinedWithNull<T[K]> }
  : T;
shrewd wigeon
#

that's odd. it seems to be just adding | undefined

remote urchin
#

try this

shrewd wigeon
#

better, that removes the undefined ( and ? ) part but doesn't make it | null

#

so maybe ? Exclude<T, undefined> | null

#

ha! it does like that!

remote urchin
#

yes

shrewd wigeon
#

ok, so I'm learning about exclude today. Seems pretty useful. Thank you for the help\

remote urchin
#

it only loops through existing properties so missing ones could still end up as undefined

shrewd wigeon
#

you mean if properties are added later?

remote urchin
#

undefinedsToNull<{ x: number, y?: string }>({ x: 1 })

#

would return a y: string|null type but at runtime y is undefined

shrewd wigeon
#

oh, you mean in the function? so if I pass in an object where name: undefined rather than name: bobby it wont work?

#

ah, it would actually put y on the object and set it null even though it didn't exist before?

remote urchin
#

nope because the for-in loop doesnt know about it

shrewd wigeon
#

oh, I see. hmm. Well there's two ways to look at this. 1) pragmatically, I don't NEED null vs undefined, but the types do. So I could call this done, or 2) theoretically, it's incorrect and we should continue.

#

I'm fine with number 1. I appreciate the help, and I"ll put a note in the code about our caveat.
Thank you !

remote urchin
#

yeah 1 is fine ig, just an edge case