Edit: Updated, minimal example (hopefully):
Paring it all down, I guess at the heart of my curiosity is why this won't work, and causes the two errors below:
interface Implicit {
someNumber?: number | { value: number }
someString?: string | { value: string }
}
interface Explicit {
someNumber?: { value: number }
someString?: { value: string }
}
const impl:Implicit = {
someNumber: 123,
someString: { value: 'impl' }
}
function convert (obj: Implicit) {
const expl:Explicit = {}
let k: keyof typeof obj;
for (k in obj) {
const objk = obj[k] // also, why does this alter behavior?
if (typeof objk === 'object') {
expl[k] = objk // Error {value:string}|{value:number} not assignable to {value:string}&{value:number}
} else {
expl[k] = { value: objk } // Error string|number is not assignable to never
}
}
}
Is TS not able to determine whether k is someNumber or someString each loop, so it's creating an intersection
enforcing the result to be both?
Previous question:
I'm trying to create a versatile function that accepts a shorthand, implicit notation { someParameter: 123 } or an explicit version: { someParameter: { value: 123, min: 0, max: 200 }}, and converts and returns the explicit version:
interface ImplicitValues {
color: number | string | ExplicitValueColor;
position: number | ExplicitValuePosition;
}
interface ExplicitOptions {
min?: number
max?: number
}
interface ExplicitValueColor extends ExplicitOptions {
value: number | string
}
interface ExplicitValuePosition extends ExplicitOptions{
value: number,
}
There are other ImplicitValue types as well (not listed for brevity), so I'd like to create a generic type, that converts to the explicit type automatically:
type MakeExplicit<T> = {
[K in keyof T]?: Extract<T[K], { value: any }>
}
This seems to work, as when we use mouse over the type using @jcalz awesome Expand utility, it is correct:
However, when we try to loop through the ImplicitValues array, and make them explicit, we get the following error:
const someObject:ImplicitValues = {
color: 0xff0000,
position: {
value: 123,
min: 0,
max: 200
}
}
function isExplicit <T>(param: any): param is MakeExplicit<T> {
if (typeof param === 'object') return 'value' in param;
return false;
}
function convertToExplicit(obj:ImplicitValues) {
let explicitValues:MakeExplicit<ImplicitValues> = {};
let key:keyof typeof obj
for (key in obj) {
if (isExplicit(obj[key])) {
explicitValues[key] = obj[key]
// ERROR: Type 'string | number | ExplicitValueColor | ExplicitValuePosition' is
// not assignable to type 'ExplicitValueColor & ExplicitValuePosition'.
} else {
explicitValues[key] = { value: obj[key] }
// ERROR: Type 'string | number | ExplicitValueColor | ExplicitValuePosition' is
// not assignable to type 'number'.
}
}
return explicitValues
}
Setting these values manually works fine, but not dynamically in a loop. Do I cast explicitValues[key] = (obj as any)[key] and get it over with, or is there a better way to do this?
Link to TS playground, if that's helpful.

isExplicit()type guard function doesn't seem to have a place from which to inferT, so it becomesunknown. You are testingobj[key]and then hoping it will be narrowed afterward, but that doesn't work in TS (see github.com/microsoft/TypeScript/issues/10530). The "unexpected intersection" is arguably due to github.com/microsoft/TypeScript/issues/30581, but there's so much other stuff going on right now. Could you pare down to a minimal reproducible example that doesn't have these other issues?Expandseems unnecessary too.ExplicitValue<T>, makingisExplicit()work, dealing withPartial, etc). If you could cut all of that out of the question so the answer doesn't have to either explain it or ignore it, that would be ideal.Implicitand turn back on--strictNullChecks, unless your question actually depends on these