Пишемо утиліту автоматичної генерації C# клієнта для проектів Wargaming

Пишемо утиліту автоматичної генерації C# клієнта для проектів Wargaming

WG API надає дуже докладний опис API, але при цьому не надає жодних бібліотек для доступу до API. Згенерувати правильні моделі з JSON не вийшло з-за особливостей структури JSON. У підсумку виявилося, що простіше написати моделі вручну, але це заняття виявилося дуже рутинною і нудною. У статті розглянемо автоматизацію створення моделі з опису HTML, а також отримані переваги і недоліки (Обережно, багато тексту)


Спойлер: Якщо хочете побачити, що вийшло, переходите відразу до Підсумками

Розробка
В якості такого прикладу на тему Xamarin зупинив свій вибір на WG API, спокусившись красивим і дуже докладним описом. На диво, не було ні однієї робочої бібліотеки, і коли почав працювати з API швидко зрозумів чому. API змінюється досить часто, при цьому, незважаючи на дуже докладну і ретельну роботу над описом, не можна сказати, що структура JSON є очевидною.

Знайшов ще проект http://code.google.com/p/wg-api-sharp-library/, але комміт нещодавно відсвяткував річницю і схоже що і далі не планує оновлюватися.

Єдиний приклад на офіційному сайті може підійти якщо потрібно зовсім небагато даних з сайту. Описаний підхід може дуже швидко втомити якщо писати повноцінний клієнт.

Дивно, що сам Wargaming, надавши настільки докладний API (відразу видно, що старалися на славу, що б так ретельно і детально документувати весь API), не подбала про те, щоб надати клієнтські бібліотеки з реалізацією для найбільш популярних мов, адже насправді, як побачимо далі, це дуже просте завдання, тим більше, якщо є доступ відразу до моделей, а не HTML опису, який виходить на виході.

Не в тему: В цілому для мене дивно, що індустрія не популяризувала аналог WSDL, який у свій час отримав велику популярність у світі корпоративних (і не тільки корпоративних) додатків. WSDL тяжеловесен зі своїм XML, але є безліч аналогів для JSON. Якби хто-небудь з них (JSON-WSP, WSDL 2.0, WADL тощо) отримав би популярність, те, на що я витратив кілька вечорів, можна було б отримати за кілька секунд, згенерувавши клієнт автоматично. Навіть наявність JSON-Schema міг би спростити рутинну роботу по генерації моделей. У результаті величезна кількість клієнтських розробників витрачають величезну кількість людино-годин на розроблення клієнтських додатків під відповідну API. Якщо дуже пощастить, то буде повний, достатній і добре структурований JSON з прикладом для того що б створити відповідні моделі автоматично. У разі WG API у нас є велика кількість даних і немає ніяких гарантій, що відповідь буде повним, так як дуже часто немає будь-яких даних у відповідних структурах. А так як структура JSON в WG API зовсім не очевидна в багатьох місцях, то згенерувати правильну модель з JSON автоматично швидше неможливо.

Парсинг HTML
Як вже згадував вище, WG API дуже докладно документований і в принципі можна однаково розпарсити всі сторінки. Перше, про що подумав — це звичайно про бібліотеки начебто htmlagilitypack і аналогах. Таке рішення дає перевагу, що рішення вийде кросплатформним, і отримане рішення можна легко запустити на будь-яких клієнтських додатках, так і в серверних додатках без UI. Але мені було простіше і швидше використовувати хромиум (CefSharp) в програмі WPF для налагодження і швидкого прототипування рішення.

Установка Cefsharp для WPF
Встановити CefSharp насправді дуже просто — необхідно просто підключити бібліотеку з nuget CefSharp.WPF. Єдине з чим можна зіткнутися і неочевидно — це те, що після установки пакета студію треба перезапускати. Так само необхідно вибрати платформу (x86 / x64) замість AnyCPU.

Далі при запуску додатка для прикладу прописав сторінку з WG API
<wpf:ChromiumWebBrowser x:Name="MyWebBrowser" Address="https://ru.wargaming.net/developers/api_reference/wot/account/info/" /> 



Переконавшись, що сторінка відображається в нашому браузері, залишилося отримати дані з усіх сторінок.

Витяг даних:
Для початку зробив клас куди буде зберігатися необхідна інформація, яка поступово розрослася до таких розмірів:

public class MethodItem 
{ 
public MethodItem() 
{ 
RequestFields=new List<RequestFieldItem>(); 
RootResponse=new ResponseClass(); 
} 

public string MethodName { get; set; } 
public string DescriptionPath { get; set; } 
public string AlertText { get; set; } 
public string DescriptionUrl { get; set; } 
public string RequestUri { get; set; } 
public string SupportedProtocol { get; set; } 
public string SupportedHttpMethod { get; set; } 
public List<RequestFieldItem> RequestFields { get; set; } 
public ResponseClass RootResponse { get; set; } 
public MethodLinkItem MethodLink { get; set; } 
} 

Після того, як ми відкрили відповідну сторінку, ми можемо витягти необхідні дані зі сторінки простими XPath запитами (якщо з якихось причин не дружите з XPath то простіше всього в звичайному браузері (наприклад, Chrome) отримувати XPath елемента вибираючи його на сторінці).

Наприклад, заголовок можна витягнути наступним чином:
await GetFromJquerySelector("#name"); 

А ім'я методу:
await GetFromJquerySelector("body>div>div.b-content.clearfix>div.b-content-column>div.b-page-header.js-page-header>div>span"); 

Де GetFromJquerySelector — допоміжний метод, який відправляє XPath запит через JQuery (на щастя, сам JQuery вже є на сторінці і інжектувати JS код не довелося).
Таким же чином можна витягнути всю інформацію зі сторінки.
Реалізація методу теж досить проста:
private async Task<string> GetFromJquerySelector(string jquerySelector)
{
var result = await GetFromJS<string>($"$('{jquerySelector}').text()");
return result?.Trim();
}

private async Task<T> GetFromJS<T>(string javaScript)
{
var selectorResult = await MyWebBrowser.EvaluateScriptAsync(javaScript);
return (T)selectorResult.Result;
}

Далі всі параметри запиту витягуємо в «плоский» список з трьох полів

public class RequestFieldItem 
{ 
public string FieldName { get; set; } 
public string FieldType { get; set; } 
public string FieldDescription { get; set; } 
} 

Пройшовши циклом по всім елементам:

var requestParamLen = await GetFromJS<int>("$('#parameters_block > table > tbody > tr').length"); 
for (var i = 1; i < = requestParamLen; i++) 
{ 
var requestItem = new RequestFieldItem(); 
requestItem.FieldName = await GetFromJquerySelector($"#parameters_block > table > tbody > tr:nth-child({i}) > td:nth-child(1)"); 
requestItem.FieldType = await GetFromJquerySelector($"#parameters_block > table > tbody > tr:nth-child({i}) > td:nth-child(2)"); 
requestItem.FieldDescription = await GetFromJquerySelector($"#parameters_block > table > tbody > tr:nth-child({i}) > td:nth-child(3)"); 
methodItem.RequestFields.Add(requestItem); 
} 

Тут MethodItem — це окремий об'єкт куди складається вся отримана інформація з конкретної сторінки. (тобто кожна сторінка — один MethodItem)

Опис відповіді зберігається в об'єкті:
public class ResponseClass
{
public ResponseClass()
{
ResponseFieldItems = new List<ResponseFieldItem>();
ResponseClasses = new Dictionary<string, ResponseClass>();
}

public string ClassName { get; set; }
public string ClassDescription { get; set; }
public List<ResponseFieldItem> ResponseFieldItems { get; set; }
public Dictionary<string, ResponseClass> ResponseClasses { get; set; }
}

Де
public class ResponseFieldItem
{

public string FieldName { get; set; }
public string FieldType { get; set; }
public string FieldDescription { get; set; }

}

Зберігає виключно примітивні поля, такі як string, number і т.п., а в ResponseClasses зберігається деревоподібна структура відповіді.
Після розбору HTML з отриманої структури MethodItem можемо зібрати клієнтський код.

Генерація C# клієнта
Із зібраної інформації можна згенерувати клієнтський коду для будь-якої мови.

При реалізації цього рішення мені як раз був необхідний клієнт на C# і, відповідно, обмірковував який API хочу отримати в результаті (остаточний варіант конвертора в C# реалізований у файлі CSConverter.cs ):

Припустимо, це буде якийсь об'єкт, у якого будуть відповідні методи для надсилання запитів до сервера.

Псевдокод для сторінки з персональними даними гравця:
var client=new WGClient(); 
client.GetPersonalData(...); 

Тут відразу віддав перевагу відмовився від варіанту з придумуванням власного імені методу так як простіше було шукати виклик за методами, вказаними в WG API ( наприклад, «account/info» для сторінки з персональними даними гравця)

Виходить наступний варіант:
client.GetAccountInfo(...); 

А щодо аргументів? А що якщо я хочу не всі передавати а тільки деякі з них?

Тобто в підсумку виходило щось на зразок
var accountInfo =await сlient.GetAccountInfo(ApplicationId: "demo", Search: "Amirchan"); 

Надалі виявилася одна дуже неприємна проблема — на жаль WG зовсім не документують структуру того, що API буде віддавати на виході. На виході ми можемо отримати як просто одиничний відповідь, так само можна отримати масив або словник. Дізнатися що ж буде на виході можна буде лише відправивши запит і отримавши відповідь у вигляді JSON, де і побачимо, як повертаються дані на виході (благо є посилання на API Explorer в кожному методі, який дозволяє дуже легко формувати запит і бачити відповідь прямо в браузері).

В кінцевому підсумку зупинився на такому варіанті: будь-який запит повинен починатися з Request+Method, а відповідь Response+Method

тобто в нашому випадку
var response=сlient.SendRequest<ResponseType>(new RequestType() 
{ 

}); 

Так як ми не знаємо, що у нас буде на виході, крім самого типу відповіді може бути відповідь у вигляді масиву або словника додав ще два додаткових методу, але який з них необхідно викликати можна дізнатися тільки подивившись структуру json у відповіді:
сlient.SendRequestArray<ResponseType>(new RequestType() 

client.SendRequestDictionary<ResponseType>(new RequestType() 

Наприклад, так як у нас в методі account/info повертається словник, для нашого прикладу у нас виходить наступний запит:
var response = await client.SendRequestDictionary<ResponseAccountInfo>(new RequestAccountInfo()
{
ApplicationId = "demo",
AccountId = 111
});

Вийшло трохи багатослівно і подвійний RequestRequest, але на цьому вирішив зупинити оптимізацію.
Далі з'ясувалося, що одні і ті ж методи використовуються в різних проектах.

Так, наприклад, метод account/list використовується як в WoT і WGN
Відповідно, імена об'єктів розрослися до:

[Request/Response][префікс проекту][ім'я методу]

А значить, приклад із запитом виростає до таких розмірів:
var response = await client.SendRequestDictionary<ResponseWotAccountInfo>(new RequestWotAccountInfo()
{
ApplicationId = "demo",
AccountId = accountId
});

Зупинимося на цьому з оптимізацією і тепер розглянемо формування самих об'єктів запитів і відповідей.
Наприклад у нас буде наступний об'єкт запиту
public class RequestWotAccountInfo 
{ 
...
} 

Так як ми витягуємо призначення цього запиту при парсингу, ми можемо додати в метод опис:
/// < summary> 
/// Персональні дані гравця 
/// https://ru.wargaming.net/developers/api_reference/wot/account/info/ 
/// < /summary> 

Так само при відправці запиту нам треба знати на яку саме адресу повинен піти запит, додамо аттрибут Method, де будемо вказувати адресу запиту:
[Method(Url = "api.worldoftanks.ru/wot/account/info/")] 

У разі виявлення помилки у запиті в тексті помилки бажано видати повідомлення з посиланням на опис методу. Для цього додамо ще один атрибут DescriptionUrl і де буде зазначатися адреса сторінки з інформацією про методі, який буде зчитуватися при формуванні тексту помилки:
[DescriptionUrl(Url = https://ru.wargaming.net/developers/api_reference/wot/account/info/)] 

Генерація полів цього класу-запиту теж не сильно відрізняється по складності. Треба привести назву поля до C# стилю, видаливши підкреслення і кожну частину слова починаючи з великої літери і при цьому зберегти оригінальну назву для серіалізації.

Наприклад для json поля access_token отримуємо назва AccessToken але при цьому нам треба зберегти інформацію про те як це поле повинно сериализовываться в JSON. Так як я волію користуватися бібліотекою Json.NET, то додав атрибут для цієї бібліотеки.

З урахуванням того, що у нас так само є докладний опис кожного поля, ми можемо додати його відразу ж у поле генерується:
/// < summary> 
///Ключ доступу до персональних даних користувача має термін дії. Для отримання ключа доступу необхідно запросити аутентифікацію. 
///string 
/// < /summary> 
[JsonProperty("access_token")] 
public string AccessToken { get; set; } 

Всі поля запиту (на відміну від полів відповіді) для простоти зроблені типу string, але що б не втратити який тип даних приймає запит в описі методу зазначається тип поля.

Так само у нас частина полів є обов'язковим і в описі позначені зірочкою, наприклад:

*account_id — Ідентифікатор облікового запису гравця
для таких полів додав атрибут FieldIsMandatory який при серіалізації перевіряє на наявність значення в цьому полі. Якщо значення немає, то відповідно викидається виключення з посиланням на опис методу з атрибута DescriptionUrl

У результаті виходить наступне опис поля для account_id:
/// < summary> 
///Обов'язковий параметер 
///Ідентифікатор облікового запису гравця 
///numeric, list 
/// < /summary> 
[JsonProperty("account_id")] 
[FieldIsMandatory] 
public string AccountId { get; set; } 

Генерація примітивних полів відповіді, насправді, відрізняється не сильно. Основна відмінність — це те, що типи WG API перетворюються на типи C#
/// < summary> 
///Ідентифікатор клану 
/// < /summary> 
[JsonProperty("clan_id")] 
public Int64? ClanId { get; set; } 

Зібравши в хеше всі типи даних, які зустрічаються в описі WG API зробив наступне зіставлення типів у словнику:
WGTypeToCSType.Add("string", "string"); 
WGTypeToCSType.Add("string list", "string[]"); 
WGTypeToCSType.Add("numeric", "Int64?"); 
WGTypeToCSType.Add("numeric, list", "Int64[]"); 
WGTypeToCSType.Add("timestamp", "int?"); 
WGTypeToCSType.Add("list of integers", "int[]"); 
WGTypeToCSType.Add("boolean", "bool"); 
WGTypeToCSType.Add("associative array", "Dictionary<string,string>"); 
WGTypeToCSType.Add("float", "double"); 
WGTypeToCSType.Add("list of strings", "string[]"); 
WGTypeToCSType.Add("list of timestamps", "int[]"); 
WGTypeToCSType.Add("timestamp/date", "int?"); 
WGTypeToCSType.Add("список словників", "Dictionary<string,string>"); 

А підтипи рекурсивно генеруються і складаються в окремий StringBuilder для того, щоб додати їх після оголошення класи:

private Tuple<string, string> GetClass(string prefix, ResponseClass classItem)
{
var modelTypeName = prefix + GetNormalizedName(new[] { classItem.ClassName });
int tab = 1;
var sb = new StringBuilder();
AppendLine(sb, tab, "public class " + modelTypeName);
AppendLine(sb, tab, "{");
tab++;
foreach (var responseField in classItem.ResponseFieldItems)
{
if (responseField.FieldDescription.Contains("Увага! Поле буде відключено."))
{
continue;
}
var fieldName = GetNormalizedName(responseField.FieldName.Split(new[] { ',', '_' }, StringSplitOptions.RemoveEmptyEntries));
sb.AppendLine();
AppendLine(sb, tab, "/// < summary>");
AppendLine(sb, tab, "///" + responseField.FieldDescription.Trim().Replace("\r\n", "\r\n///").Replace("\n", "\n///"));
AppendLine(sb, tab, "/// < /summary>");
AppendLine(sb, tab, $@"[JsonProperty(""{responseField.FieldName}"")]");
Append(sb, tab, "public ");
sb.Append(GetTypeString(responseField.FieldType));
sb.Append(" ");
sb.Append(fieldName);
sb.AppendLine(" {get; set;}");
}
var subClass = new StringBuilder();
foreach (var chieldClass in classItem.ResponseClasses.Values)
{
if (chieldClass.ClassDescription.Contains("Увага! Поле буде відключено."))
{
continue;
}
var createClassModel = GetClass(modelTypeName, chieldClass);
var typeName = createClassModel.Item1;
var classModel = createClassModel.Item2;
subClass.Append(classModel);
var fieldName = GetNormalizedName(chieldClass.ClassName.Split(new[] { ',', '_' }, StringSplitOptions.RemoveEmptyEntries));
sb.AppendLine();
AppendLine(sb, tab, "/// < summary>");
AppendLine(sb, tab, "///" + chieldClass.ClassDescription.Trim().Replace("\r\n", "\r\n///").Replace("\n", "\n///"));
AppendLine(sb, tab, "/// < /summary>");
AppendLine(sb, tab, $@"[JsonProperty(""{chieldClass.ClassName}"")]");
Append(sb, tab, "public ");
sb.Append(typeName);
sb.Append(" ");
sb.Append(fieldName);
sb.AppendLine(" {get; set;}");
}
tab--;
AppendLine(sb, tab, "}");
tab--;
AppendLine(sb, tab, subClass.ToString());
return new Tuple<string, string>(modelTypeName, sb.ToString());
}



Підсумки:
Зрештою, у нас вийшло дуже просте додаток, де по черзі відкриваються сторінки з WG API і распарсиваются. Після цього генерується і виводиться C# код клієнта в окремому вікні:


Вихідний код можете завантажити тут:

Якщо не хочете або немає можливості завантажити проект і запустити його, то там же можете завантажити приклад згенерованого коду клієнта.

Приклад використання:
Исходники наведеного нижче прикладу можна забрати тут

У нас вийшов код який сумісний з PCL, тому ми можемо використовувати цей код як для клієнтських додатків, так і серверних веб додатків.

Для прикладу витратив 15 хвилин і створив невелике «голе» Xamarin Forms додаток без дизайну, де накидав, у першій формі пошук по ніку, а у другій формі інформацію про обраному ніке серед знайдених.

У створений проект додав cs файл, куди скопіював код , згенерований нашої утилітою.

Наступний крок — додавання бібліотеки Json.net (пошук в nuget бібліотеки Newtonsoft.Json або командою Install-Package Newtonsoft.Json c nuget консолі).

Код пошуку виходить досить простим:
var client = new WGClient.Client(); 
var accounts = await client.SendRequestArray<ResponseWgnAccountList>(new RequestWotAccountList() 
{ 
ApplicationId = "demo", 
Search = SearchNickname 
}); 
GamerAccounts = accounts; 

При цьому, завдяки згенеровані опису, у нас є можливість отримувати підказки прямо в студії при наборі коду:



Зверніть увагу, ApplicationId: «demo» можна використовувати тільки для тестування API. Для релізу необхідно створити свій ApplicationId особистому кабінеті

Тепер залишилося відобразити список знайдених Nickname:

На жаль, у мене вже немає свого аккаунта, спасибі Шериеву Світові (мого брата) за наданий ігровий нік для розтерзання в прикладах:

По тапу зі списку знайдених відкриваємо другу форму, передавши вибраний AccountId:
var item = e.SelectedItem as ResponseWgnAccountList; 
Navigation.PushAsync(new DetailsPageView(item.AccountId)); 

На другій сторінці теж створюється запит до іншого методу для одержання детальнішої інформації:
var client=new Client(); 
var response=await client.SendRequestDictionary<ResponseWotAccountInfo>(new RequestWotAccountInfo() 
{ 
ApplicationId = "demo", 
AccountId = accountId 
}); 



Таким чином, за допомогою нашого згенерованого клієнта у нас є можливість заощадити величезну кількість часу на автоматизацію рутини формування запиту і відповіді до WG API і зосередити час, сили і увагу на сам додаток.

Недоліки:
Необхідність ручного допиливания напилком:
Мабуть це самий основний недолік — як і для всього відповіді в цілому так і для підтипів немає ніякої інформації, як будуть повернуті дані (простий відповідь, масив або словник). Тому в багатьох місцях доведеться робити правки.

Возмьем для прикладу метод Техніка (encyclopedia/vehicles)

Для зменшення обсягу відповіді відфільтруємо відповідь по техніці одного рівня і одного нації:

Виклик наступного коду викличе помилку:
var client = new WGClient.Client(); 
var response = await client.SendRequestDictionary<ResponseWotEncyclopediaVehicles>(new RequestWotEncyclopediaVehicles() 
{ 
ApplicationId = "demo", 
Tier = "8", 
Nation = "ussr" 
}); 

викинувши виняток:

Unhandled Exception: Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'WGClient.WorldOfTanks.WotEncyclopediaVehiclesCrew' because the type requires a JSON object (e.g. {«name»:«value»}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {«name»:«value»}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.

Звідси випливає, що не вдалося десериализовать підтип Crew, і якщо ми побудуємо запит API Explorer

То побачимо, що Crew повертається у вигляді масиву



При цьому поле Engines коректно розпізналося завдяки тому, що для нього був вказаний тип поля «list of integers» на відміну від підтипу Crew, для якого немає ніякої інформації.

Виправити помилку можна зробивши поле Crew масивом, замінивши поле
/// < summary> 
///Екіпаж 
/// < /summary> 
[JsonProperty("crew")] 
public WotEncyclopediaVehiclesCrew Crew { get; set; } 

на масив:
public WotEncyclopediaVehiclesCrew[] Crew { get; set; } 

Аналогічну помилку отримуємо для default_profile.ammo, відповідно, там теж необхідно виправити зробивши масив.

Залежність від HTML
Так як WG API не використовує ніяких стандартів опису JSON доводиться задовольнятися парсингом HTML опису. А HTML може змінюватися довільним чином, а опис одних і тих же типів можуть відрізнятися, і зустрічаються навіть російською мовою, тобто немає жодних гарантій, що завтра не з'явиться нова назва використовуваного типу.

Розвиток
Так як на рішення і приклад було витрачено приблизно пара вільних вечорів і спочатку створювався проект який буде не шкода викинути і побудувати на його базі нормальне рішення, є маса можливостей для розвитку цього проекту. Перерахуємо деякі з них:

Браузер
Поточне рішення побудоване на базі CefSharp що вже означає що рішення буде працювати тільки на платформі Win32. Можна переписати з використанням бібліотек CefSharp, щоб отримати кроссплатформенной рішення.

Якщо залишатися на парсингу HTML, то можливо має сенс переписати на TypeScript, в такому разі можна буде зробити рішення не тільки на клієнта, але і у вигляді плагіна до хрому.

Поганий інтернет
Парсер не враховує, що інтернет може пропасти з усіма витікаючими наслідками.

Неоптимальне API клієнта
В кінченому підсумку зупинився на досить багатослівному і не дуже зручному великоваговому варіанті
SendRequest<TResponse>(TRequest request) 

Є маса способів зробити рішення простіше. Але максимально простий варіант можна було б отримати, якщо з опису API було б зрозуміло повернеться відповідь у вигляді словника, масиву або одиничного об'єкта.

Nuget або необхідність вручну додавати бібліотеку Json.NET
На жаль, на даний момент немає можливості генерувати рішення у вигляді складання або nuget пакета.

Рішення з Nuget пакетом могло б позбавити від необхідності копіювати вихідний код і підключати вручну Json.NET але як вже було сказано вище, місцями доведеться допілівать напилком.

Покриття тестами
1-2 рази в місяць виходить нова версія API. Відповідно перегенерировав весь відповідь ми автоматично втратимо всі правки напилком, які у нас вже були зроблені.

Для того, щоб переконатися, що в новій версії все коректно допрацювали напилком, можна було б покрити тестами отримане рішення для того, щоб переконатися, що не забули допилити напилком що небудь. За великим рахунком, можна було б автоматично генерувати тести. В теорії це дозволить частково переконатися в тому, що нова версія не разломает геть.

Якість коду
Як і було сказано вище, код писався як прототип рішення, який потім не шкода буде викинути і переписати в робочий варіант. На всі рішення (включаючи приклад написаний за 15 хвилин) було витрачено сумарно пара вечорів.

Вибір проекту для генерації клієнта
На поточний момент генерується більше 40 тисяч рядків для всіх проектів відразу. Можна було б додати вибір яких проектів і яких методів цього проекту необхідно згенерувати клієнт.

На даний момент всі проекти поділені namespace-ами і можна просто видалити проект і тим самим зменшити кінцевий розмір збірки.



Те ж саме стосується зайвих полів.

Можна нескінченно займатися поліпшенням, але на цьому можна підвести резюме

Резюме
Сам факт того що Wargaming намагається бути відкритим до розробників і намагається не просто відкривати API, але ще і детально описує значення полів дуже похвальна. Незважаючи на істотні недоліки в описі і в самій структурі відповіді (а саме те що не можна зрозуміти що буде повернений, одиночний відповідь, масив або словник для складних типів), це одне з кращих документацій API для подібних сервісів.

Тим не менш, з якихось своїх причин Wargaming не слід ніяким стандартам для свого JSON, який дозволив би автоматизувати процес генерації клієнта заощадивши розробникам величезну масу сил і часу на написання рутинного коду.

Мало того, як ми бачили з цієї статті, WG якщо і не слід стандартам, то міг би легко генерувати клієнтські бібліотеки хоча б для декількох основних мов програмування, що могло б заощадити величезну кількість часу і сил для розробників шляхом простого підключення готової бібліотеки. Будемо сподіватися що, побачимо подібні рішення від WG (тим більше, що самої WG немає необхідності парсити HTML). Можливо, це допоможе суттєво збільшити кількість користувачів цього API.

Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.