Skip to content

x.json2: @[json_null] field attribute ignored on encode (option fields with none always omitted) #26905

@enghitalo

Description

@enghitalo

Describe the bug

The encoder in x.json2 ignores the @[json_null] field attribute. With the legacy json (cJSON) encoder, an Option field annotated with @[json_null] is emitted as "field":null instead of being skipped when the value is none. x.json2 always omits none fields and provides no way to opt-in to writing JSON null.

V code

import x.json2

struct Foo {
	name   ?string @[json_null]
	age    ?int    @[json_null]
	other2 ?string @[json_null]
}

fn main() {
	println(json2.encode(Foo{}))
}

C backend result

// Encoder writes nothing for `none` Option fields — the @[json_null]
// attribute is never read in the encoder.
// Output: {}

Reproduction Steps

import x.json2

struct Bar {
	name ?string @[json_null]
}

struct Foo {
	name   ?string @[json_null]
	age    ?int    @[json_null]
	text   ?string
	other  ?Bar
	other2 ?Bar @[json_null]
}

fn test_json_null_attribute() {
	assert json2.encode(Foo{}) == '{"name":null,"age":null,"other2":null}'
	assert json2.encode(Foo{ name: '' }) == '{"name":"","age":null,"other2":null}'
	assert json2.encode(Foo{ age: 10 }) == '{"name":null,"age":10,"other2":null}'
	assert json2.encode(Foo{
		age:    10
		other2: Bar{
			name: none
		}
	}) == '{"name":null,"age":10,"other2":{"name":null}}'
}

Expected Behavior

PASS test_json_null_attribute

Current Behavior

> assert json2.encode(Foo{}) == '{"name":null,"age":null,"other2":null}'
  Left value (len: 2):  `{}`
  Right value (len: 38): `{"name":null,"age":null,"other2":null}`

Possible Solution

In the struct encoder loop, when iterating fields with $for field in T.fields and detecting field.typ is $option:

  1. If the option is none, check whether 'json_null' in field.attrs.
  2. If yes, emit "<key>":null, instead of skipping the field.
  3. Apply the same rule to embedded option struct values (the other2 ?Bar @[json_null] case in the failing test) and to nested option struct fields (Bar{ name: none }{"name":null}).

Cache this attribute lookup in cached_field_infos by adding an is_json_null bool field to EncoderFieldInfo so the comptime cost is paid once per type.

Additional Information/Context

This is feature parity with the cJSON json_is_null_attr test, which already passes against the cJSON-based json module. Without this attribute, downstream APIs that distinguish "absent" vs "null" (e.g. JSON Merge Patch, Stripe API style) cannot be expressed in 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