最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - Narrow variable of type unknown into Record of unknown - Stack Overflow

programmeradmin4浏览0评论

I am trying to narrow a variable of type unknown to Record<PropertyKey, unknown> without using type assertions, but I am getting the following error;

Type 'object' is not assignable to type 'Record<PropertyKey, unknown>'. Index signature for type 'string' is missing in type '{}'.ts(2322)

How can I improve the following code without using a type assertion or custom type guard (like variable is Record<PropertyKey, unknown>) ? The only other question I found didn't appear to have a concrete answer.

const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
  if (
    typeof variable !== "object" ||
    Array.isArray(variable) ||
    variable === null
  ) {
    throw new Error("Not Record");
  }

  return variable
};

I believe I need to tell the compiler that there is at least one property of either string, number or symbol on the narrowed object, but I'm not sure how.


And for posterity, if I need to also support the possibility of parsing an empty object, can I use the same signature, or would I need to do something like Record<PropertyKey, unknown> | {}? I ask because unknown[] neatly covers both an empty array and a populated array.

I am trying to narrow a variable of type unknown to Record<PropertyKey, unknown> without using type assertions, but I am getting the following error;

Type 'object' is not assignable to type 'Record<PropertyKey, unknown>'. Index signature for type 'string' is missing in type '{}'.ts(2322)

How can I improve the following code without using a type assertion or custom type guard (like variable is Record<PropertyKey, unknown>) ? The only other question I found didn't appear to have a concrete answer.

const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
  if (
    typeof variable !== "object" ||
    Array.isArray(variable) ||
    variable === null
  ) {
    throw new Error("Not Record");
  }

  return variable
};

I believe I need to tell the compiler that there is at least one property of either string, number or symbol on the narrowed object, but I'm not sure how.


And for posterity, if I need to also support the possibility of parsing an empty object, can I use the same signature, or would I need to do something like Record<PropertyKey, unknown> | {}? I ask because unknown[] neatly covers both an empty array and a populated array.

Share Improve this question asked Mar 6 at 12:37 myolmyol 10k24 gold badges97 silver badges158 bronze badges 4
  • 1 No need for an additional union type (| {}) as Record<PropertyKey, unknown> already supports empty objects. Instead, you can check for non-empty objects by using the following code: if (Object.keys(variable).length === 0) { throw new Error("Empty object not allowed"); } . – I_Al-thamary Commented Mar 6 at 12:58
  • 1 This is ms/TS#38801. For now there's no way to have it happen automatically, you'll need to assert or write a custom type guard function (which is effectively the same, although it's at least reusable) as shown in this playground link. Does this fully address the question? If so I'll write up a full answer or find a duplicate; if not, what's missing? – jcalz Commented Mar 6 at 13:11
  • 1 @jcalz thank you for the issue link! Now I know its a TS limitation and impossible without assertion, I will just use that. Both comments answered my questions, thank you. – myol Commented Mar 6 at 13:29
  • Right, but comments are ephemeral and don't count as answers, I was just looking for clarification to make sure I understood the issue. I'll write up a full answer. – jcalz Commented Mar 6 at 13:50
Add a comment  | 

2 Answers 2

Reset to default 1

TypeScript currently does not narrow from the unknown type to Record<PropertyKey, unknown>. The closest you can get is narrowing to the object type, but since object is apparently not assignable to Record<PropertyKey, unknown>, this doesn't help you much. There is a longstanding open feature request at microsoft/TypeScript#38801 to allow the narrowing you're looking for, but it hasn't been implemented and apparently it might be a breaking change if it were, so for now it's not part of the language.

You can always just assert than an object is a Record<PropertyKey, unknown>,

const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
    if (
        typeof variable !== "object" ||
        Array.isArray(variable) ||
        variable === null
    ) {
        throw new Error("Not Record");
    }
    return variable as Record<PropertyKey, unknown>
};

or, if you think you'll need such narrowing to happen in lots of places, you could refactor it to a custom type guard function which always returns true (since we presume that checking if a value is a non-null object is sufficient):

function objectIsRecordUnknown(o: object): o is Record<PropertyKey, unknown> {
    return true; // this is effectively always true
}

const parseAsRecord = (variable: unknown): Record<PropertyKey, unknown> => {
    if (
        typeof variable !== "object" ||
        Array.isArray(variable) ||
        variable === null ||
        !objectIsRecordUnknown(variable) // no-op
    ) {
        throw new Error("Not Record");
    }
    return variable
};

Then objectIsRecordUnknown(variable) is always true, but now it allows TS to narrow from object as desired. If you're only doing the narrowing once, though, this is just a more roundabout way of using a type assertion (it's no safer).

Playground link to code

Seems there's no narrowing for Record<PropertyKey, any> => Record<PropertyKey, unknown>. You could provide all interested types for the argument and narrow them out:

Playground

const parseAsRecord = (variable: Record<PropertyKey, unknown> | string | number | boolean | bigint | null | undefined | unknown[]) => {
  if (
    typeof variable !== "object" ||
    Array.isArray(variable) ||
    variable === null
  ) {
    throw new Error("Not Record");
  }

  return variable;
};

发布评论

评论列表(0)

  1. 暂无评论