TypeScript: странное поведение при переключении между PascalCase и camelCase

Я рефакторинг настольного приложения С# в веб-приложение Angular/TypeScript.

Все свойства класса внутри приложения C# используют PascalCase, поэтому я подумал, что было бы неплохо просто сохранить его.

Вот 2 примера практически одного и того же класса TypeScript. Номер 1 использует PascalCase, номер 2 использует camelCase:

//PascalCase
export class Info 
{
    public ManagedType:string;

    public ApiTemplate:string;
}

//camelCase
export class Info 
{
    public managedType:string;

    public apiTemplate:string;
}

Вот странное поведение:

  1. Я загружаю данные JSON с веб-сервера и создаю массив вышеуказанного класса Info. Кажется, не имеет значения, использует ли класс TypeScript PascalCase или camelCase. Все идет нормально.

    this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
    
  2. Когда я вывожу свой массив в консоль, я вижу, что выходные данные используют camelCase для свойств, независимо от того, использует ли класс Info PascalCase или camelCase. Немного странно, но пока все хорошо.

  3. Теперь вот мне стало странно: Когда я использую PascalCase и фильтрую массив, чтобы получить один конкретный экземпляр класса Info, результат всегда неопределен/нуль.

  4. Когда я использую camelCase и фильтрую массив, чтобы получить один конкретный экземпляр класса Info, результат найден и верен.

    //This doesn't work: Info is always undefinded, although the Array exists.
    let Info = Infos.filter(i => i.ManagedType == "Something" && i.ApiTemplate == "Something else")[0];
    
    //This works: Info is found 
    let Info = Infos.filter(i => i.managedType == "Something" && i.apiTemplate == "Something else")[0];
    

Мои вопросы:

Почему это так? Это проблема TypeScript или проблема Angular?

Есть ли неписаное соглашение, которому я должен следовать?

Почему компилятор TypeScript не выдает ошибку или предупреждение о том, что использование PascalCase может работать некорректно?

typescript все равно в любом случае. В итоге все конвертируется в javascript. Я думаю, что проблема может быть связана с данными, которые ваш сервер возвращает во время выполнения. Проверьте свои http-запросы, чтобы увидеть, как сервер отправляет данные, и соответствующим образом назовите свои свойства.

toskv 04.04.2019 00:30

Поступающие данные используют camelCase, но я не понимаю, почему локальный класс, в котором хранятся данные, определенные в TypeScript/JavaScript, должен или должен использовать то же имя, что и файл JSON. В этом нет никакого смысла (кроме автоматического сопоставления)

Michael 04.04.2019 00:35

Вы создаете экземпляры своего класса, используя new, или вы используете данные сервера напрямую, но утверждаете, что тип — это тот, который вы определили? Если вы не создаете экземпляры, вы получаете объекты json. Typescript присутствует только во время сборки, он транспилируется в javascript, и это то, что работает во время выполнения.

toskv 04.04.2019 00:39

Нет, я не использую new() и не создаю новые экземпляры. Я использую данные сервера напрямую. Код выглядит следующим образом: this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();

Michael 04.04.2019 00:44

@toskv Еще не проверено, но это уже может быть ответом. Мне приходится перебирать данные сервера и создавать новые экземпляры, не полагаясь на автоматическое сопоставление. Я попробую это. Очень странно то, что автомаппинг работает правильно, независимо от того, использую ли я PascalCase или camelCase. На мой взгляд, это должно вызвать ошибку, говорящую о том, что данные не могут быть правильно сопоставлены. Исходя из фона С#, это просто странно.

Michael 04.04.2019 00:58

Я отредактировал свой вопрос, чтобы показать, как я загружаю данные с веб-сервера. См. пункт 1.)

Michael 04.04.2019 01:08
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
6
937
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Why is that so? Is that a TypeScript issue or is this an Angular issue?

Ни один. Причина проблемы в том, что данные json, поступающие с вашего веб-сервера, не имеют точно такой же структуры/формата, в котором вы определили информацию о классе в машинописном тексте.

Is there an unwritten convention that I have to follow?

Ну да есть. Вы должны вручную протестировать и убедиться, что вы действительно получаете правильные структуры данных, прежде чем приводить их к конкретным классам. Чтобы уточнить, вы должны взять json (тело ответа HTTP), проанализировать его как JSON для универсального объекта, а затем проверить его, действительно ли он имеет все свойства (с тем же именем и типами) как класс (информация) что вы собираетесь их бросить. А затем сделайте это.

ОБНОВЛЕНИЕ: на самом деле есть классный способ определить, является ли объект конкретный тип и сообщите об этом машинописному сценарию, обеспечивая надежную защиту/защиту типа. Typescript имеет эту функцию, называемую Пользовательские функции Typeguard, где вы определяете функцию, которая возвращает true или false, если объект проверяется на принадлежность к определенному типу.

// user-defined type-guard function
function isInfo(obj: Object): obj is Info {
    if ('ManagedType' in obj && 'ApiTemplate' in obj) {
        return true;
    } else {
    // object does not have the required structure for Info class
        return false;
    }
}

// lets assume that jsonString is a string that comes from an
// http response body and contains json data. Parse it "blindly" to a generic object
let obj = JSON.parse(jsonString);

if (isInfo(obj)) {
    obj.ApiTemplate; // typescript in this scope knows that obj is of type Info
} else {
    // in this scope, typescript knows that obj is NOT of type Info
}

Why doesn't the TypeScript compiler throw an error or a warning, that using PascalCase may not work properly?

Поскольку вы используете неявное приведение, когда вы используете this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();, вы говорите машинописи, что «эй, я знаю, что во время выполнения сервер отправит строку json, которая будет проанализирована и будет абсолютно точно совместима с Info[] (массивом информационных объектов ). Но на самом деле во время выполнения этого не происходит, потому что есть небольшая разница в чувствительности имен свойств к регистру. Typescript не будет ошибаться здесь, потому что вы неявно сказали ему, что знаете, что делаете.

Итак, чтобы уточнить:

Очевидно, что во время выполнения вы преобразуете объект JSON, который не полностью совместим с определением класса Info, к которому вы его неявно привели. Данные json на самом деле имеют имена свойств с camelCase, но вы определили класс Info с PascalName. Взгляните на этот пример:

//PascalCase
class Info 
{
    public ManagedType:string;

    public ApiTemplate:string;
}

let jsonString = `{
    "managedType": "1234asdf",
    "apiTemplate": "asdf1234"
}`;

// And HERE IS THE ISSUE. This does an implicit cast to Info object
// assuming that the JSON parsed object will strictly be the same as defined Info
// class. But that is not guaranteed. Typescript just assumes that you know
// what you are doing and what kind of data you will actually get in 
// runtime.
let obj: Info = JSON.parse(jsonString); 

Последняя строка приведенного выше примера выполняет точно такое же «слепое» преобразование/преобразование, что и эта:

this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();

По сути, вы сообщаете машинописному тексту, что ответ будет представлять собой классы Array of Info, определенные точно так же, как определение класса, но на самом деле в реальных данных json это не так, поэтому JSON.parse() вернет объект, который имеет имена свойств точно такие же, как в строке json, в camelCase вместо PascalCase, которые вы позволяете машинописному тексту принимать.

// typescript just assumes that the obj will have PascalCase properties 
// and doesn't complain. but in reality this at runtime will not work,
// because the json properties in the json string are camelCase. Typescript
// can not know what data you will actually cast to this type in runtime.
// and cannot catch this error
console.info(`accessing Info.ManagedType property: ${obj.ManagedType}`);

// lets check at runtime all the actual properties names
// they will be in camelCase, just like in the jsonString.
Object.keys(obj).forEach(key => {
    console.info(`found property name: ${key}`);
});

Спасибо. Я думаю, что это было бы отличной помощью/идеей, если бы это было возможно, чтобы выдать ошибку времени выполнения или предупреждение, если разработчик не хочет слепого кастинга, потому что, исходя из С#, это определенно вызовет ошибку... ..но ладно, так оно и есть....Спасибо за все комментарии и ответы!

Michael 04.04.2019 01:27

спасибо за принятие. Я также обновил свой ответ конкретными ответами на каждый из ваших вопросов. не стесняйтесь спрашивать что-нибудь еще для получения дополнительных разъяснений.

J. Koutsoumpas 04.04.2019 01:31

Другие вопросы по теме