Skip to content

RichText deserialization fails for plain strings: “JSON value does not match Telegram union” #369

Closed

Description

@nesc1

Title

RichText deserialization fails for plain strings: “JSON value does not match Telegram union”

Description

Summary

tgbot-cpp 1.13 throws std::invalid_argument when deserializing a RichText value represented as a plain JSON string.

I encountered this while calling TgLongPoll::start(). The debugger shows that a paragraph object's required text field contains a string, but RichText::value only includes formatted-object alternatives.

Environment

  • tgbot-cpp: 1.13, installed through Conan 2
  • Operating system: Linux
  • Application language standard: C++23
  • nlohmann/json: 3.12.0
  • Reproduced with a Debug build of tgbot-cpp

Minimal Reproduction

The following example isolates the failing conversion from polling and requires no bot token:

#include <tgbot/Json.h>
#include <tgbot/Types.h>

#include <exception>
#include <iostream>

int main()
{
    const nlohmann::json input = "plain text";
    TgBot::RichText value;

    try
    {
        TgBot::from_json(input, value);
    }
    catch (const std::exception& exception)
    {
        std::cerr << exception.what() << '\n';
        return 1;
    }
}

The corresponding paragraph JSON observed during debugging has this structure:

{
  "type": "paragraph",
  "text": "plain text"
}

The standalone reproduction above is derived from the debugger findings; I have not run it separately yet.

Expected Behavior

According to the Telegram Bot API documentation, RichText can be:

  • A string for plain text.
  • An array of RichText.
  • One of the listed formatted-text objects.

Valid strings should deserialize successfully, including when nested in a paragraph's text field. Arrays should also be supported.

Actual Behavior

Deserialization throws:

JSON value does not match Telegram union

The relevant call sequence, omitting the repeated variant-decoding frames, is:

Json::readRequiredField(..., "text", ...)
  → Json::decode<std::shared_ptr<RichText>>(...)
  → from_json(..., RichText&)
  → Json::decode<0>(..., variant)
  → ... attempts all 26 alternatives ...
  → Json::decode<26>(...)
  → throws std::invalid_argument

At the failing breakpoint:

  • The JSON value is a string.
  • The containing object has type = "paragraph".
  • The destination variant remains at its default alternative, an empty std::shared_ptr<RichTextBold>.

Suspected Cause

RichText::value contains 26 formatted-object alternatives, but no alternative for std::string or an array of RichText.

The generic variant decoder therefore cannot match a plain string. Its discriminator checks reject each formatted-object candidate, eventually producing the generic union exception.

The RichText definition in master also appeared to have the same omission when checked.

Impact and Suggested Fix

The exception prevents the polling iteration from completing successfully. Retrying without fixing the conversion can repeatedly encounter the same pending update.

Please consider adding plain-string and recursive-array representations to RichText, ensuring both JSON decoding and encoding support them, and updating the API generator so the fix survives regeneration.

Regression tests covering strings, arrays, formatted objects, and a paragraph containing plain text would help prevent recurrence.

Activity

  1. reo7sp commented on Nov 3, 2026

    @reo7sp
    Owner

    Fixed #370

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions