# Serializace odpovědí (JSON) a konvertory SDK ## Problém Endpointy vracející některé modely z IdokladSdk končily chybou 500 a v logu byla výjimka: ```text System.NotImplementedException: The method or operation is not implemented. at IdokladSdk.Serialization.DateTimeConverter.WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConvertable(...) ``` Příčiny: - Část modelů SDK má konvertor přiřazený atributem `[JsonConverter(...)]`. - Dva z těchto konvertorů umí jen čtení (deserializaci odpovědí z iDokladu), jejich `WriteJson` vyhazuje `NotImplementedException`: - `IdokladSdk.Serialization.DateTimeConverter` u 14 položek typu `DateTime` - `IdokladSdk.Serialization.NotificationJsonConverter` přímo u typu `NotificationListGetModel` - Služba používá Newtonsoft.Json i pro výstup (`AddNewtonsoftJson`), takže při serializaci odpovědi Newtonsoft konvertor z atributu použil a request spadl. Konvertor uvedený atributem má přednost před konvertory registrovanými v `SerializerSettings`, takže pouhé přidání vlastního konvertoru do nastavení problém neřeší. Vyměnit se musí na úrovni kontraktu, tedy v `IContractResolver`. Postižené bylo 14 modelů SDK, mimo jiné: ```text ReceivedInvoiceGetModel, ReceivedInvoiceListGetModel, BankAccountGetModel, BankAccountListGetModel, CashRegisterGetModel, CashRegisterListGetModel, CreditNoteGetModel, CreditNoteListGetModel, IssuedInvoiceCopyGetModel, ProformaInvoiceCopyGetModel, RecurringSettingGetModel, RecurringSettingListGetModel, SubscriptionGetModel, NotificationListGetModel ``` Prakticky to znamená, že padaly například `GET /received-invoices`, `GET /registers/bank-accounts` a `GET /integration/notifications`. ## Řešení | Soubor | Účel | | --- | --- | | `Infrastructure/SdkWriteBypassJsonConverter.cs` | Obal nad konvertorem SDK. Čtení deleguje na něj, `CanWrite` je `false`, takže zápis provede standardní serializace po položkách. | | `Infrastructure/SdkContractResolver.cs` | Potomek `DefaultContractResolver`, který tímto obalem nahradí konvertory SDK neumějící zápis. | | `Program.cs` | Registrace resolveru v `AddNewtonsoftJson`. | Detaily: - Nahrazují se jen konvertory ze jmenného seznamu (`DateTimeConverter`, `NotificationJsonConverter`). Konvertory SDK, které zápis umí, například `NullablePropertyJsonConverter` u `NullableProperty`, zůstávají beze změny. - Nahrazuje se konvertor na položce (`Converter`), na kolekci (`ItemConverter`) i na typu (`JsonContract.Converter`), protože `NotificationJsonConverter` je uvedený u typu. - Čtení se nemění, `CanRead` i `ReadJson` se delegují na původní konvertor SDK. Deserializace requestů je tedy stejná jako předtím. - Zápis datumů se neformátuje vlastním způsobem. Jde stejnou cestou jako u položek `DateTime`, které konvertor nikdy neměly, takže je v odpovědi jednotný tvar. - Původní `NamingStrategy` z výchozího resolveru MVC se přenese, aby se nezměnily názvy vlastností v JSON odpovědích. ## Ověření Provedeno před nasazením: 1. `dotnet build -c Release` bez chyb. 2. Serializace všech 536 veřejných typů SDK s bezparametrickým konstruktorem, se stejným nastavením, jaké dostane `AddNewtonsoftJson`: - před opravou selhalo 14 typů na `NotImplementedException` - po opravě 0 typů - 521 typů serializovatelných v obou případech dalo identický JSON, názvy vlastností se tedy nezměnily 3. Round trip: serializace a zpětná deserializace modelu zachovala všech 7 položek `DateTime`. 4. Čtení payloadu ve tvaru iDokladu (`"DateOfIssue":"2026-08-25T00:00:00"`) dává stejný výsledek před i po opravě. 5. Běh služby proti falešnému iDoklad API (`IDOKLAD_API_URL` a `IDOKLAD_IDENTITY_URL`): `GET /health` vrátil 200, `GET /docs/v1/swagger.json` 200 a `GET /received-invoices`, který dříve padal, vrátil 200 s vyplněnými datumy. Po nasazení stačí zavolat: ```text GET https://services.csbot.cz/apps/idoklad/received-invoices?page=1&pageSize=1 ``` Očekávaný výsledek je HTTP 200 a v logu žádná `NotImplementedException`. ## Poznámka mimo rozsah opravy Pokud iDoklad vrátí odpověď, kterou SDK neumí zpracovat, vyhodí `IdokladValidationException`. Ta není potomkem `IdokladBaseException`, takže ji `ExceptionHandlingMiddleware` nezachytí a request skončí jako neošetřená 500 místo 502. Zjištěno při testu proti falešnému API, neopravováno, protože to nesouvisí se serializací odpovědí.