Как выразить тип записи «только для чтения» в машинописном тексте, который является ковариантным с совместимыми интерфейсами?

#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 } без махинаций с дженериками. Может быть, есть проблема / предложение, которого мне не хватает, если я что — то откопаю, обновлю ответ с информацией.