Я рефакторинг настольного приложения С# в веб-приложение 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;
}
Вот странное поведение:
Я загружаю данные JSON с веб-сервера и создаю массив вышеуказанного класса Info. Кажется, не имеет значения, использует ли класс TypeScript PascalCase или camelCase. Все идет нормально.
this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
Когда я вывожу свой массив в консоль, я вижу, что выходные данные используют camelCase для свойств, независимо от того, использует ли класс Info PascalCase или camelCase. Немного странно, но пока все хорошо.
Теперь вот мне стало странно: Когда я использую PascalCase и фильтрую массив, чтобы получить один конкретный экземпляр класса Info, результат всегда неопределен/нуль.
Когда я использую 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 может работать некорректно?
Поступающие данные используют camelCase, но я не понимаю, почему локальный класс, в котором хранятся данные, определенные в TypeScript/JavaScript, должен или должен использовать то же имя, что и файл JSON. В этом нет никакого смысла (кроме автоматического сопоставления)
Вы создаете экземпляры своего класса, используя new, или вы используете данные сервера напрямую, но утверждаете, что тип — это тот, который вы определили? Если вы не создаете экземпляры, вы получаете объекты json. Typescript присутствует только во время сборки, он транспилируется в javascript, и это то, что работает во время выполнения.
Нет, я не использую new() и не создаю новые экземпляры. Я использую данные сервера напрямую. Код выглядит следующим образом: this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
@toskv Еще не проверено, но это уже может быть ответом. Мне приходится перебирать данные сервера и создавать новые экземпляры, не полагаясь на автоматическое сопоставление. Я попробую это. Очень странно то, что автомаппинг работает правильно, независимо от того, использую ли я PascalCase или camelCase. На мой взгляд, это должно вызвать ошибку, говорящую о том, что данные не могут быть правильно сопоставлены. Исходя из фона С#, это просто странно.
Я отредактировал свой вопрос, чтобы показать, как я загружаю данные с веб-сервера. См. пункт 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}`);
});
Спасибо. Я думаю, что это было бы отличной помощью/идеей, если бы это было возможно, чтобы выдать ошибку времени выполнения или предупреждение, если разработчик не хочет слепого кастинга, потому что, исходя из С#, это определенно вызовет ошибку... ..но ладно, так оно и есть....Спасибо за все комментарии и ответы!
спасибо за принятие. Я также обновил свой ответ конкретными ответами на каждый из ваших вопросов. не стесняйтесь спрашивать что-нибудь еще для получения дополнительных разъяснений.
typescript все равно в любом случае. В итоге все конвертируется в javascript. Я думаю, что проблема может быть связана с данными, которые ваш сервер возвращает во время выполнения. Проверьте свои http-запросы, чтобы увидеть, как сервер отправляет данные, и соответствующим образом назовите свои свойства.