In order for this to work, Parent must be a union representing the different ways to meet your constraint. Essentially either nested.a is present and c is absent, or nested.a is absent and c is present. For a property to be absent you can make it optional and have its property value type be the never type (this often allows the undefined type in there but that's hopefully not an issue). For a property to be present you should make it (and any parent properties) required.
For your example code that looks like
type Parent =
{ nested: { a: string, b?: string }, c?: never } |
{ nested?: { a?: never, b?: string }, c: string }
And you can verify that it works as desired:
let p: Parent;
p = { nested: { b: "abc" }, c: "def" } // okay
p = { nested: { a: "abc", b: "def" } } // okay
p = { nested: { b: "abc" } } // error
p = { nested: { a: "abc", b: "def" }, c: "ghi" } // error
p = { nested: {}, c: "abc" } // okay
p = { c: "abc" } // okay
Note that I didn't reuse your original Nested definition. If you really need to you can use it along with utility types like Pick/Omit and Partial/Required:
type Parent =
{ nested: Required<Pick<Nested, "a">> & Omit<Nested, "a">, c?: never } |
{ nested?: { a?: never } & Omit<Nested, "a">, c: string }
But this only really makes sense if Nested has a lot of properties in it, and even then you might want to refactor:
interface BaseNested {
b?: string
// other props
}
interface NestedWithA extends BaseNested {
a: string
}
interface NestedWithoutA extends BaseNested {
a?: never
}
type Parent =
{ nested: NestedWithA, c?: never } |
{ nested?: NestedWithoutA, c: string }
Playground link to code