1

How do I have a declaration inside an interface that enforces an object to extend another?

Scenario: I receive a JSON object (called document) where the document.children is an array of objects that will always have _id, _ref and _type, as well as additional fields that are specific to its _type.

Currently There are 4 different types but this will grow and ideally i don't want future devs having to worry about editing the Document interface

export interface BaseRefs {
  _id: string;
  _ref: string;
  _type: string;
}

export interface Span extends BaseRefs {
  text: string;
}

export interface MainImage extends BaseRefs {
  url: string;
  caption: string;
}

export interface Document extends BaseRefs {
  _createdAt: string;
  _rev: string;
  _updatedAt: string;
  children: // Any object that extends BaseRefs
  // children: MainImage | Span | Carousel | ........ | Video[] // This is not ideal
}

export const document: Document = {
  _createdAt: '2019-12-12T04:14:18Z',
  _id: 'c9b9-4cd0-a281-f6010f5889fd',
  _ref: 'ej2gcz5m4',
  _rev: 'nwyfsi--ej2gcz5m4',
  _type: 'post',
  _updatedAt: '2020-01-16T11:22:32Z',
  children: [
    {
      _type: 'span',
      text: 'The rain in Spain',
    },
    {
      _type: 'mainImage',
      url: 'https://example.com/kittens.png',
    },
  ],
};

1 Answer 1

1

How do I have a declaration inside an interface that enforces an object to extend another?

You can't do that literally. TypeScript's type system is structural, not nominal. You can require that it have all the properties defined by BaseRefs, but not that it actually extends BaseRefs.

As far as I can tell, the type of children should be BaseRefs[]:

export interface Document extends BaseRefs {
  _createdAt: string;
  _rev: string;
  _updatedAt: string;
  children: BaseRefs[];
}

That requires that the elements in children all have all of the properties defined by BaseRefs. They can have more properties, so Span and MainImage and such are fine, but they must at least have the properties defined for BaseRefs.

That means you can safely use _id, _ref, and _type on elements in children, but if you try to use something else (like Span's text property), TypeScript will complain that there is no text property on BaseRefs.

for (const child of someDocument.children) {
    console.log(child.text);
    //                ^−−−−−−−−−−−−−−−−−− error here
}

That's because the child may not be a Span or, more generally, may not have a text property.

You gain access to the property by using a type guard. One kind of type guard is a type guard function, which looks like this:

function isSpan(x: BaseRefs): x is Span {
    return "text" in x;
}

Then:

for (const child of someDocument.children) {
    if (isSpan(child)) {
        console.log(child.text);
        //                ^−−−−−−−−−−−−−−−−−− works
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.