Unsupported variant on json union type using atdgen

Hello everyone

I’m trying out Ocaml and i am facing the following issue:

Trying to define an atd file which can handle 2 possible values on a specific field (specifically an SQS response)

using the following atd file:

type sqs_response = {
	response <json name="ListQueuesResponse">: list_queues_response ;
}

type list_queues_response = {
	?queues_result <json name="ListQueuesResult">: queue_urls_result option;
}


type queue_urls_result = {
	queue_urls <json name="QueueUrl">: [StringList of string list | String of string] ;
}

I can not handle the case of having 1 queue in the response or more.
If the number of queues returned using ListQueues is 1, we get the following response:

{
  "ListQueuesResponse": {
    "ListQueuesResult": {
      "QueueUrl": "http://localhost:4566/000000000000/sample-queue2"
    },
    "ResponseMetadata": {
      "RequestId": "KGNI0QTGU2CPWDXWNDXYNMYAEN6RG7452QD3WG1PIR9WR5GIFA3L"
    }
  }
}

If there are more queues, the response is:

{
  "ListQueuesResponse": {
    "ListQueuesResult": {
      "QueueUrl": [
        "http://localhost:4566/000000000000/sample-queue2",
        "http://localhost:4566/000000000000/sample-queue1"
      ]
    },
    "ResponseMetadata": {
      "RequestId": "3BFOG8GBPN3YJX0A9BQ3T7EP7WWNBSQIH5UKYHSV5KAF77F28H15"
    }
  }
}

In both cases, i get the following runtime error:

Fatal error: exception Atdgen_runtime.Oj_run.Error("Line 1:\nUnsupported variant \"http://localhost:4566/000000000000/sample-queue2\"")

the generated code is this:

type queue_urls_result = {
  queue_urls: [ `StringList of string list | `String of string ]
}

type list_queues_response = { queues_result: queue_urls_result option }

type sqs_response = { response: list_queues_response }

What am i missing?

Thanks in advance

1 Like

Since the post was hidden for some days, i would like to bump this.

Sorry if bumping is not permitted

It’s been a while since I spent much time with ATD but I believe the default representation of variants is as a list of one or two elements with the first representing the variant type. see the notes in the OCaml Support - atdgen — atd documentation

I’m not sure if it’s the easiest approach but you can solve it with a simple custom adapter.
https://atd.readthedocs.io/en/latest/atdgen.html#field-adapter-ocaml

3 Likes

Just as an example, the way that you currently have your atd spec written, to successfully parse with by the generated code, QueueUrl would need to look like this in the single string case:

"QueueUrl": ["String", "http://localhost:4566/000000000000/sample-queue2"]

or like this for string list:

"QueueUrl": [
  "StringList", [
    "http://localhost:4566/000000000000/sample-queue2",
    "http://localhost:4566/000000000000/sample-queue1"
  ]
]

Note that it would look like that if you pass in the -j-std flag.

1 Like

Thanks both for the input. I left atd and used ppx_yojson_conv where i implemented pretty much what you both suggested which i found easier to create a deserializer for a specific field.

If it is not too much trouble, would you mind summarizing your solution (eg show code)? It would be helpful for future reference for people with similar issue. (Interestingly, this question is now one of the top google search results for atdgen variant, so it will likely be seen.)

sure

what i ended up doing is switching firstly to ppx_deriving_yojson and later to ppx_yojson_conv after seeing that the latter is the “newer” version and not using atdgen at all (i suppose there are multiple ways to deal with json in ocaml ecosystem)

then i wrote manually the records

type queue_urls_result = {
  queue_urls : Ha.t; [@key "QueueUrl"] [@of_yojson Ha.t_of_yojson]
}
[@@deriving of_yojson]

type list_queues_response = {
  queues_result : queue_urls_result option; [@key "ListQueuesResult"]
}
[@@yojson.allow_extra_fields] [@@deriving of_yojson]

type sqs_response = {
  response : list_queues_response; [@key "ListQueuesResponse"]
}
[@@deriving of_yojson]

and used a custom deserializer for a specific field

essentially this: [@of_yojson Ha.t_of_yojson]

and inside Ha i have the following

type t = StringList of string list | String of string 
[@@deriving yojson, show]

let t_of_yojson = function
  | `List value -> StringList (Yojson.Safe.Util.filter_string value)
  | `String v -> String v
  | _ -> StringList []
1 Like