Home > OS >  TypeScript: as const string array field causing readonly error
TypeScript: as const string array field causing readonly error

Time:06-30

The following example has a ts error at test

type Test = {
    obj: object;
    arr: string[];
};

export const test: Test = {
    obj: {},
    arr: ['foo'],
} as const;

saying:

Type '{ readonly obj: {}; readonly arr: readonly ["foo"]; }' is not assignable to type 'Test'. Types of property 'arr' are incompatible. The type 'readonly ["foo"]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.

I want to use as const (causing the error) with test cuz test should never be changed. At the same time I will have other mutable variables of type Test, so I can't have any of the fields be readonly. How do I make the error go away with my goal in mind?

Confusingly, obj causes no error. Arrays are technically objects.

CodePudding user response:

You would have to add readonly to the type of the field arr in Test.

type Test = {
    str: string;
    arr: readonly string[];
};

Edit: given your preference to keep the Test arr field mutable, you could try something like this. Make arr a union type of both string[] and readonly string[].

However, you will immediately run into other issues, because if TypeScript doesn't know which type arr has for a given instance of Test, it will assume the most restrictive behavior and enforce that you can't actually mutate the array.

The solution is to have utility functions like pushIfNotReadonly() below which can use type guards to ensure the given array is in fact mutable before trying to do such an operation. But the "failure" behavior is kind of undefined - maybe you just don't push the value into the array, but how do you account for that tricky behavior in your logic? You'll quickly lose track of what is happening, useful type checking goes out the window and you get yourself into a mess.

What you're trying to do just isn't very compatible with TypeScript patterns and should be avoided if you can. I recommend you rethink how you are using type definitions and what your fundamental intent is, and evaluate other options.

type Test = {
    obj: object;
    arr: string[] | readonly string[];
};

export const test1: Test = {
    obj: {},
    arr: ['foo'],
} as const;

export const test2: Test = {
    obj: {},
    arr: ['bar'],
}

function pushIfNotReadonly(arr: Test['arr'], value: string) {
    if (Array.isArray(arr)) {
        arr.push(value)
    }
}

pushIfNotReadonly(test1.arr, 'string'); // will not push because it is readonly
pushIfNotReadonly(test2.arr, 'string'); // will push because it is NOT readonly

CodePudding user response:

This seems to work:

type Test = {
    obj: object;
    arr: string[];
};

export const test: Test = {
    obj: {},
    arr: ['foo'] as string[]
} as const;

CodePudding user response:

type Test = {
  str: string;
  arr: string[];
};

export const test: Test = {
  str: 'Hello',
  arr: ['foo'] as string[],
} as const;

If you used as const with Test then you need to define type inside read-only and for more details refer to this document TypeScript Read Only

CodePudding user response:

Could you make use of the Readonly utility type?

Readonly Documentation

Playground Link

type Test = {
    obj: object;
    arr: string[];
};

const test: Readonly<Test> = {
    obj: {},
    arr: ['foo'],
}

// Error
test.obj = {}

// Error
test.arr = []

This leaves your original type untouched but does not allow your variant to be changed.

  • Related