Skip to content

x.json2: encoder omits _type discriminator field for sumtype values, breaking round-trip #26904

@enghitalo

Description

@enghitalo

Describe the bug

When encoding a sumtype value with x.json2, the produced JSON does not include the discriminator field _type that the existing json (cJSON-based) encoder always adds. This breaks round-trip encode → decode and breaks compatibility with any consumer expecting the _type discriminator that the legacy json module emits and consumes.

x.json2.encode_sumtype simply forwards the inner variant to encode_value without writing any discriminator key.

V code

import x.json2

type Animal = Cat | Dog

struct Cat {
	cat_name string
}

struct Dog {
	dog_name string
}

fn main() {
	out := json2.encode(Animal(Dog{ dog_name: 'Caramelo' }))
	println(out)
}

Reproduction Steps

import x.json2

type Animal = Cat | Dog

struct Cat {
	cat_name string
}

struct Dog {
	dog_name string
}

fn test_sumtype_emits_type_discriminator() {
	out := json2.encode(Animal(Dog{ dog_name: 'Caramelo' }))
	assert out == '{"dog_name":"Caramelo","_type":"Dog"}'
}

Expected Behavior

PASS test_sumtype_emits_type_discriminator

Current Behavior

> assert out == '{"dog_name":"Caramelo","_type":"Dog"}'
  Left value (len: 23): `{"dog_name":"Caramelo"}`
  Right value (len: 37): `{"dog_name":"Caramelo","_type":"Dog"}`

Possible Solution

In the encoder (encode_sumtype and the corresponding object writer used for struct variants), after writing the inner struct fields:

  1. Detect that the value's static type is a sumtype variant of a struct.
  2. Before closing the object }, append ,"_type":"<VariantTypeName>" (use T.name of the matched variant).
  3. For non-struct variants (int, string, bool, time.Time), use the same wrapping the cJSON encoder uses — time.Time becomes {"_type":"Time","value":<unix>}, primitives stay bare (cJSON keeps them bare too).
  4. Mirror this in the decoder: when decoding a sumtype struct variant, prefer the _type key when present and fall back to structural matching only if it is missing (the []StringOrComplexValue test depends on the structural fallback that already exists in the decoder for the no-_type case).

V's legacy C codegen for the json module already does this when expanding sumtype encoders — that logic should be ported to x.json2.

Additional Information/Context

The same root cause makes the json_sumtype todo test under x.json2, the converted cJSON sumtype encode test, and the cJSON test_encode_sumtype_defined_ahead case all fail when they target x.json2.

V version

V 0.5.1 1b3385cc34ff783e793d1a26a8ec5be587c80fe0.40b3711

Environment details (OS name and version, etc.)

|V full version      |V 0.5.1 1b3385cc34ff783e793d1a26a8ec5be587c80fe0.40b3711
|:-------------------|:-------------------
|OS                  |linux, Ubuntu 24.04 LTS
|Processor           |16 cpus, 64bit, little endian, AMD Ryzen 7 5800H with Radeon Graphics
|Memory              |8.17GB/30.7GB
|                    |
|V executable        |/home/hitalo/Documents/v/v
|V last modified time|2026-04-18 09:18:00
|                    |
|V home dir          |OK, value: /home/hitalo/Documents/v
|VMODULES            |OK, value: /home/hitalo/.vmodules
|VTMP                |OK, value: /tmp/v_1000
|Current working dir |OK, value: /home/hitalo/Documents/v
|                    |
|Git version         |git version 2.43.0
|V git status        |0.5.1-1006-g40b3711b-dirty
|.git/config present |true
|                    |
|cc version          |cc (GCC) 14.2.0
|gcc version         |gcc (GCC) 14.2.0
|clang version       |Ubuntu clang version 18.1.3 (1)
|tcc version         |tcc version 0.9.28rc 2025-02-13 HEAD@f8bd136d (x86_64 Linux)
|tcc git status      |thirdparty-linux-amd64 696c1d84
|emcc version        |emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.6 ()
|glibc version       |ldd (Ubuntu GLIBC 2.39-0ubuntu8.3) 2.39

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugThis tag is applied to issues which reports bugs.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions