We have a structure that is like the following:
export type LinkRestSource = {
model: string;
rel?: string;
title?: string;
} | {
model?: str
Maybe something like that:
type X<A, B, C> = (A & Partial<B> & Partial<C>) | (Partial<A> & B & Partial<C>) | (Partial<A> & Partial<B> & C);
type LinkRestSource = X<{ model: string }, { rel: string }, { title: string }>
var d: LinkRestSource = {rel: 'sdf'};
But it little bit messy :)
or
type Y<A, B, C> = Partial<A & B & C> & (A | B | C);
A simpler version of the solution by jcalz:
type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
so the whole implementation becomes
type FullLinkRestSource = {
model: string;
rel: string;
title: string;
}
type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
type LinkRestSource = AtLeastOne<FullLinkRestSource>
const okay0: LinkRestSource = { model: 'a', rel: 'b', title: 'c' }
const okay1: LinkRestSource = { model: 'a', rel: 'b' }
const okay2: LinkRestSource = { model: 'a' }
const okay3: LinkRestSource = { rel: 'b' }
const okay4: LinkRestSource = { title: 'c' }
const error0: LinkRestSource = {} // missing property
const error1: LinkRestSource = { model: 'a', titel: 'c' } // excess property on string literal
and here's the TS playground link to try it
I think I have a solution for you. You're looking for something that takes a type T
and produces a related type which contains at least one property from T
. That is, it's like Partial<T>
but excludes the empty object.
If so, here it is:
type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
To dissect it: first of all, AtLeastOne<T>
is Partial<T>
intersected with something. U[keyof U]
means that it's the union of all property values of U
. And I've defined (the default value of) U
to be a mapped type where each property of T
is mapped to Pick<T, K>
, a single-property type for the key K
. (For example, Pick<{foo: string, bar: number},'foo'>
is equivalent to {foo: string}
... it "picks" the 'foo'
property from the original type.) Meaning that U[keyof U]
in this case is the union of all possible single-property types from T
.
Hmm, that might be confusing. Let's see step-by-step how it operates on the following concrete type:
type FullLinkRestSource = {
model: string;
rel: string;
title: string;
}
type LinkRestSource = AtLeastOne<FullLinkRestSource>
That expands to
type LinkRestSource = AtLeastOne<FullLinkRestSource, {
[K in keyof FullLinkRestSource]: Pick<FullLinkRestSource, K>
}>
or
type LinkRestSource = AtLeastOne<FullLinkRestSource, {
model: Pick<FullLinkRestSource, 'model'>,
rel: Pick<FullLinkRestSource, 'rel'>,
title: Pick<FullLinkRestSource, 'title'>
}>
or
type LinkRestSource = AtLeastOne<FullLinkRestSource, {
model: {model: string},
rel: {rel: string},
title: {title: string}>
}>
or
type LinkRestSource = Partial<FullLinkRestSource> & {
model: {model: string},
rel: {rel: string},
title: {title: string}>
}[keyof {
model: {model: string},
rel: {rel: string},
title: {title: string}>
}]
or
type LinkRestSource = Partial<FullLinkRestSource> & {
model: {model: string},
rel: {rel: string},
title: {title: string}>
}['model' | 'rel' | 'title']
or
type LinkRestSource = Partial<FullLinkRestSource> &
({model: string} | {rel: string} | {title: string})
or
type LinkRestSource = {model?: string, rel?: string, title?: string} &
({model: string} | {rel: string} | {title: string})
or
type LinkRestSource = { model: string, rel?: string, title?: string }
| {model?: string, rel: string, title?: string}
| {model?: string, rel?: string, title: string}
which is, I think, what you want.
You can test it out:
const okay0: LinkRestSource = { model: 'a', rel: 'b', title: 'c' }
const okay1: LinkRestSource = { model: 'a', rel: 'b' }
const okay2: LinkRestSource = { model: 'a' }
const okay3: LinkRestSource = { rel: 'b' }
const okay4: LinkRestSource = { title: 'c' }
const error0: LinkRestSource = {} // missing property
const error1: LinkRestSource = { model: 'a', titel: 'c' } // excess property on string literal
So, does that work for you? Good luck!
There's another solution if you know which properties you want.
AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>
This would also allow you to lock in multiple keys of a type, e.g. AtLeast<T, 'model' | 'rel'>
.
That would not be possible with Partial<T>
. Under the hood it looks like this:
type Partial<T> = { [P in keyof T]?: T[P]; };
All properties made optional.
I doubt it is possible (or easy) to enforce your rule via type system.
Could try to create type that employs keyof
in a similar way, but have a condition in default constructor.
If you can think of a way to declare a type like Partial
that builds a matrix of types like yours emitting ?
for a different key in each and concat all of them using |
like in your first example, you might be able to enforce your rule vie type system.
This blog post on keyof might give you some ideas.