Skip to content
Advertisement

How do I define Type with strings versus object literal properties?

I’ve poured over the TS docs, but can’t seem to find an answer as to why the following is illegal.

I’m able to define a union type with strings, yet if I create the same union type with references to object properties, which ultimately references the same string values, the compiler isn’t happy.

Demo via typescript playground

// Success
export type SortOrderWithStrings = 'asc' | 'desc';

export const SortOrderStatic = {
  ASC: 'asc',
  DESC: 'desc',
}

// Fail
export type SortOrderWithObject = SortOrderStatic.ASC | SortOrderStatic.DESC;

The errors are as follows. Why is this a namespace issue?

Cannot find namespace 'SortOrderStatic'
Exported type alias 'SortOrderReference' has or is using private name 'SortOrderType'

Ultimately, it’d be nice to have a Column interface the defines the supported sort order types, while then instancing a Column with SortOrderStatic to avoid magic strings, but per the above, this doesn’t seem possible.

interface Column {
  label: string;
  defaultSortOrder: SortOrderWithObject;
}

const MyColumn: Column = {
  label: 'My Column',
  defaultSortOrder: SortOrderStatic.ASC
}

If I define the interface as follows (with SortOrderWithStrings), and then attempt to use SortOrderStatic, the compiler once again isn’t happy.

interface Column {
  label: string;
  defaultSortOrder: SortOrderWithStrings;
}

const MyColumn: Column = {
  label: 'My Column',
  defaultSortOrder: SortOrderStatic.ASC
}

Fails with:

Type 'string' is not assignable to type 'SortOrderWithStrings'.

It’s clear I’m missing something fundamental here, and I appreciate any insight.

Advertisement

Answer

It’s clear I’m missing something fundamental here

Yes, you are confusing compile-time with runtime: unless the compile-time type is a literal string, the compiler cannot guarantee that the runtime access of an object property is the string you think it is. Your object property is type string, which means at runtime it could be any string.

The string literal is immutable (and so statically verifiable), the object property access not so much.

In your particular case I would just use an enum:

enum SortOrder {
  ASC = 'ASC',
  DESC = 'DESC',
}

export { SortOrder }

import { SortOrder } from 'something';
SortOrder.ASC; // ASC as a string

This will maintain type-safety, you can’t pass any ol’ string to something that expects a SortOrder: (Playground)

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement