#typescript #covariance #typescript-generics
Вопрос:
Мне нужно определить метод, который сериализует объекты для хранения, но его семантика требует, чтобы он мог работать только с объектами, ключи полей которых имеют строковый тип (поскольку они должны соответствовать именам столбцов, поэтому они не могут быть числами).
Использование Record<string, unknown>
типа не будет работать для этого ввода сериализатора, потому что я предоставляю ему объекты, для которых определены определенные поля (например interface Foo {foo: number, bar: string}
, и они не могут быть назначены Record<string, unknown>
).
То же самое, почему мы не можем безопасно назначить a List<Cat>
для a List<Animal>
, если List
оно изменчиво, но мы можем, если они неизменны .
Поэтому мне нужно либо указать, что параметр универсального типа keyof T
является подмножеством string
, либо иметь ReadOnlyRecord<string, unknown>
ковариантный с любым интерфейсом, имеющим строковые ключи.
Предложения?
Комментарии:
1. единственное, в чем я не уверен в вашем вопросе, так это в том, где
readonly
он вступает в игру. Я надеюсь, что ответ именно тот, который вы искали, но если нет, не могли бы вы, пожалуйста, предоставить ссылку на игровую площадку с образцом желаемого результата?2. да, это не совсем то, что я имел в виду, я имел в виду не
readonly
свойство, а скорее неизменяемость, как в замороженной карте/коллекции, которая может быть безопасно назначена картам/коллекциям общих типов с верхней границей.3. Я тоже не был ясен в своем запросе 🙂 Я имел в виду то же самое, что и вы, и
readonly
ключевое слово здесь не используется в качестве модификатора. Похоже, вы можете применить решение к своему варианту использования, но если вы не возражаете удовлетворить мое любопытство — не могли бы вы позже опубликовать ссылку на игровую площадку о том, как все вместе выглядит?4. @OlegValter на самом деле, я мог бы поспешить сказать, что получил то, что хотел… это немного более запутанно (с участием другого слоя универсальных классов), но я постараюсь обновить конечный результат 🙂
5. не беспокойтесь 🙂 Есть ли что-нибудь о задаче, с которой вы могли бы воспользоваться помощью?
Ответ №1:
Здесь вам не нужно принимать во внимание различия. Причина Record<string, unknown>
сбоя заключается просто в том, что утилита требует, чтобы ее параметр типа имел подпись индекса. По той же причине метод не может рассчитывать на получение типа, назначаемого в { [x:string]: unknown }
качестве его единственного параметра.
В любом случае, я не думаю, что вы сможете это сделать без параметров универсального типа. Затем вам просто нужно проверить, можно ли назначить тип с ключом только для строки { [P in Extract<keyof T, string> : any }
, например, переданному типу (очевидно, что другой способ всегда будет передаваться, поскольку ваши интерфейсы являются подтипами первого).
Обратите внимание, что X extends T ? T : never
в параметре необходимо, чтобы компилятор мог делать выводы T
из использования, сохраняя при этом ограничение. Единственное предостережение заключается в том, что вы не сможете перехватывать symbol
свойства (но сможете, если их тип есть unique symbol
).:
{
class Bar {
public baz<T extends object>(serializable: { [P in Extract<keyof T,string>] : any } extends T ? T : never) { return JSON.stringify(serializable); }
}
const foo: Foo = { foo: 42, bar: "answer" };
const nope = { [1]: true };
const symb = { [Symbol("ok")]: 24 };
const US: unique symbol = Symbol("unique");
const usymb = { [US]: Infinity };
const bar = new Bar();
bar.baz(foo); //OK
bar.baz(nope); //Argument of type '{ 1: boolean; }' is not assignable to parameter of type 'never'
bar.baz(symb); //OK? Inferred as `{ [x: string]: number; }`
bar.baz(usymb); //Argument of type '{ [US]: number; }' is not assignable to parameter of type 'never'
}
Комментарии:
1.
Extract
Универсальный тип-это та часть головоломки, которую мне не хватало, я думаю, что смогу адаптировать ее к своему варианту использования! Большое спасибо!2. @fortran — рад, что это работает для вашего варианта использования! Иногда мне хочется, чтобы мы могли выразить что-то подобное
{ [ only strings but not an index signature ] : any }
без махинаций с дженериками. Может быть, есть проблема / предложение, которого мне не хватает, если я что — то откопаю, обновлю ответ с информацией.