Vonage/vonage-dotnet-sdk

Messages | DynamicObject serialization fails

Closed this issue · 3 comments

Tr00d commented

After changing the serializer for MessagesClient from Newtonsoft.Json to System.Text.Json, request serialization is failing under specific circumstances.
As shown below, a request with dynamic content (<dynamic[]>) ends up partially serialized by the client. It happens because the components property is a deserialized dynamic object from Newtonsoft.Json. The following request produces a JObject, while the serializer expects a JsonElement.

The real problem is loose typing with a dynamic object, as I assume it would work with strongly typed objects.

Request:

new WhatsAppCustomRequest
{
    From = "XXXXX",
    To = "XXXXX",
    Custom = new
    {
        type = "template",
        template = new
        {
            @namespace = "c106bb35_2a66_4432_9d19_bffa061064ec",
            name = "test_name",
            language = new
            {
                code = "he",
                policy = "deterministic",
            },
            components = JsonConvert.DeserializeObject<dynamic[]>(
                "[{\"type\": \"body\",\"parameters\":[{\"type\":\"text\",\"text\":\"hello\"},{\"type\":\"text\",\"text\":\"world\"}]},{\"type\": \"button\",\"index\": \"0\",\"sub_type\": \"url\",\"parameters\": [{\"type\": \"text\",\"text\": \"123456789\"}]}]"),
        },
    },
}

Serialized:

{
  "channel": "whatsapp",
  "message_type": "custom",
  "to": "XXXXX",
  "from": "+XXXXX",
  "custom": {
    "type": "template",
    "template": {
      "namespace": "c106bb35_2a66_4432_9d19_bffa061064ec",
      "name": "test_name",
      "language": {
        "code": "he",
        "policy": "deterministic"
      },
      "components": [
        {
          "type": [],
          "parameters": [
            [
              [
                []
              ],
              [
                []
              ]
            ],
            [
              [
                []
              ],
              [
                []
              ]
            ]
          ]
        },
        {
          "type": [],
          "index": [],
          "sub_type": [],
          "parameters": [
            [
              [
                []
              ],
              [
                []
              ]
            ]
          ]
        }
      ]
    }
  }
}

One possible solution could be to rollback to Newtonsoft.Json.

EDIT: Updated after ongoing investigation

if you are using System.Text.Json then this seems to work:

var components = JsonSerializer.Deserialize<dynamic[]>(
               "[{\"type\": \"body\",\"parameters\":[{\"type\":\"text\",\"text\":\"hello\"},{\"type\":\"text\",\"text\":\"world\"}]},{\"type\": \"button\",\"index\": \"0\",\"sub_type\": \"url\",\"parameters\": [{\"type\": \"text\",\"text\": \"123456789\"}]}]");
string json = JsonSerializer.Serialize(components);
Tr00d commented

Hi @epicqed,

Indeed. After more investigation, the problem comes from using two different serializers: newtonsoft on the request creation and System.Text.Json on the request serialization. The following issue is that dynamic values are not populated using the same objects.

I don't recommend using dynamic types in general, but I don't have any control over what's getting in the SDK. As such, moving to System.Text.Json for Messages is considered a breaking change.

Tr00d commented

As we talked about internally, this issue won't be fixed.
The library we use for serializing requests is an implementation detail and is, therefore, hidden from users. As such, we can upgrade/switch our dependency as we see fit.

We do not recommend providing serializer-specific items in an object property (like JObject or JsonElement) because it may produce issues.
In the above we situation (TL;DR: provide a deserialized JSON string as dynamic[], producing items of type JObject that our serializer doesn't recognize), there are a few options:

  • [recommended] Avoid dynamic and favour a concrete object instead. For example, record types are quite handy and could be a one-liner in your codebase.
  • Avoid using the SDK for this particular call to keep control over the request serialization.

In the future, we'll keep this scenario in mind. When accepting unstructured objects, offering the possibility to provide JSON could be interesting. There's obviously a downside, given we would have to provide two options, and most users won't use Jsonified string.