Объект с непредсказуемыми полями в интерфейсе TypeScript

Я работаю над веб-приложением в Angular и делаю следующий вызов службы return this.http.get<SiteContent>(this.apiUrl + this.endpoint + id) Проблема в том, что JSON, который сопоставляется с интерфейсом SiteContent, содержит объект в одном из этих полей, где содержимое непредсказуемо. Как я могу обработать это в своем коде, чтобы иметь доступ к этим полям в поле siteContent? в идеале я хочу сделать что-то вроде этого

return this.http.get<SiteContent>(this.apiUrl + this.endpoint + id).subscribe((obj) => {
    console.info(obj.siteContent.get("randomKey"));
})

Приведенный выше пример приводит к следующему сообщению об ошибке, когда я пытаюсь выполнить get() по ключу.

ERROR TypeError: obj.siteContent.get is not a function

Интерфейс SiteContent

export interface SiteContent {
    id: string;
    name: string;
    siteContent: Map<string,string>
}

Пример JSON

{"id":"abc123","name":"name name","siteContent":{"random1":"hi","random2":"hey"}}

Пример: https://www.typescriptlang.org/play?target=7&ts=4.6.2#code/JYOwLgpgTgZghgYwgAgMrEgYQPbguZAbwFgAoZC5YAEwC5kBnMKUAcwG4zLkQ4BbCPSYsQHLpQYYIOPOHoBZOAAcAPMLYAadaIB8ZAL5kyAGwhhkAKwa5kAXmQApVAHkAcgDolcKAwgAKcQoAckIAIhpQ2lC4ACMEAEYAJgBmUI1Q3gFIjP4UTIg00MksXEhwSLCoOBBqbD547IALYEKqmrrEpogAT1D9fSDA5ABKZDgGNCkZMrAyBFxrU3djbFY-K1x3YulS-DB3VjM-ULba+tDh4fYgA

🤔 А знаете ли вы, что...
Один из ключевых принципов Angular - это двунаправленное связывание данных, которое автоматически обновляет интерфейс при изменении данных и наоборот.


71
1

Ответ:

Решено

Фон JavaScript, не относящийся к TypeScript:

JSON — это формат данных, основанный на нотации JavaScript литерал объекта. Когда вы используете метод JSON.parse() для десериализации строки JSON в значение JavaScript, вы получите тот же тип объектов, что и с литералами объектов JavaScript:

let foo = { "a": 1, "b": "two", "c": false };
console.info(foo) // { "a": 1, "b": "two", "c": false }

let bar = JSON.parse('{ "a": 1, "b": "two", "c": false }');
console.info(bar) // { "a": 1, "b": "two", "c": false }

Объекты в JavaScript представляют собой пары ключ-значение, где ключи (обычно) являются строками, но значения могут быть любого типа. Эти ключи не нужно объявлять заранее, что касается JavaScript. Все объекты в JavaScript являются объектами «expando»..

Вы получаете доступ к свойству объекта либо с помощью записи через точку (если имя ключа является известным допустимым идентификатором):

foo.a;

или с помощью записи в квадратных скобках (обычно только в том случае, если имя ключа не является допустимым идентификатором или если оно является результатом некоторого выражения):

foo["a"];

const k = "a";
foo[k]; 

Вы не используете get() для доступа к свойствам таких объектов. Существует являетсякласс JS Map, который хранит пары ключ-значение и имеет метод get() для извлечения таких пар. Но Map не то же самое, что простой объект JS. Есть причины использовать Maps, но обработка десериализованного JSON обычно не входит в их число.

Поэтому во время выполнения у вас должен быть такой код:

let json = JSON.parse(
    '{"id":"abc123","name":"name name","siteContent":{"random1":"hi","random2":"hey"}}'
);

console.info(json.siteContent.random1); // hi

TypeScript добавляет статическую систему типов для описания JavaScript. Как правило, он не добавляет никакой функциональности и не меняет способ работы JavaScript. Тип interface, например SiteContent, — это способ описания формы определенных объектов JavaScript, с которыми вы ожидаете взаимодействовать во время выполнения.

Типы объектов в TypeScript могут иметь известные свойства с определенными ключами строкового литерала, например id или name. Однако также часто используются объекты JavaScript в качестве объектов Expando, где свойства имеют известный тип значения, но ключи не известны заранее. Итак, TypeScript позволяет вам присвоить типу объекта подпись индекса. Тип объекта {[k: string]: number} означает, что «вы можете индексировать этот объект с любым ключом k типа string, и если там есть свойство, оно будет типа number. В вашем случае вы хотите, чтобы свойство siteContent было типом объекта, ключи произвольны, но значения которых равны strings:

interface SiteContent {
    id: string;
    name: string;
    siteContent: { [k: string]: string }
}

Вооружившись этой сигнатурой индекса, ваш код теперь будет компилироваться в TypeScript без ошибок:

let json = JSON.parse(
    '{"id":"abc123","name":"name name","siteContent":{"random1":"hi","random2":"hey"}}'
) as SiteContent;

console.info(json.siteContent.random1); // hi

Ссылка на код для игровой площадки