From 9adeb6c49f0c64e33c0d810d48e5612b7ef4d6c3 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 05:21:25 +0300 Subject: [PATCH] x.json2: fix @[json_null] field attribute ignored on encode (fixes #26905) --- vlib/x/json2/encode.v | 8 +++++++- vlib/x/json2/tests/attributes_test.v | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/vlib/x/json2/encode.v b/vlib/x/json2/encode.v index 838909d0e..a82ed111b 100644 --- a/vlib/x/json2/encode.v +++ b/vlib/x/json2/encode.v @@ -499,6 +499,7 @@ struct EncoderFieldInfo { is_skip bool is_omitempty bool is_required bool + is_json_null bool } fn get_value_from_optional[T](val ?T) T { @@ -557,6 +558,7 @@ fn (mut encoder Encoder) cached_field_infos[T]() []EncoderFieldInfo { mut key_name := '' mut is_omitempty := false mut is_required := false + mut is_json_null := false for attr in field.attrs { match attr { 'skip' { @@ -569,6 +571,9 @@ fn (mut encoder Encoder) cached_field_infos[T]() []EncoderFieldInfo { 'required' { is_required = true } + 'json_null' { + is_json_null = true + } else {} } @@ -586,6 +591,7 @@ fn (mut encoder Encoder) cached_field_infos[T]() []EncoderFieldInfo { is_skip: is_skip is_omitempty: is_omitempty is_required: is_required + is_json_null: is_json_null } } } @@ -633,7 +639,7 @@ fn struct_field_should_encode[T](field_info EncoderFieldInfo, val T) bool { return false } } - if !field_info.is_required && struct_field_is_none(val) { + if !field_info.is_required && !field_info.is_json_null && struct_field_is_none(val) { return false } if struct_field_is_nil(val) { diff --git a/vlib/x/json2/tests/attributes_test.v b/vlib/x/json2/tests/attributes_test.v index cc93a220a..e1d37298b 100644 --- a/vlib/x/json2/tests/attributes_test.v +++ b/vlib/x/json2/tests/attributes_test.v @@ -46,12 +46,37 @@ struct Foo { a int @[required] } +struct JsonNullAttrBar { + name ?string @[json_null] +} + +struct JsonNullAttrFoo { + name ?string @[json_null] + age ?int @[json_null] + text ?string + other ?JsonNullAttrBar + other2 ?JsonNullAttrBar @[json_null] +} + fn test_last_field_requiered() { assert json.decode[Foo]('{"a":0}')! == Foo{ a: 0 } } +fn test_json_null_attribute() { + assert json.encode(JsonNullAttrFoo{}) == '{"name":null,"age":null,"other2":null}' + assert json.encode(JsonNullAttrFoo{ name: '' }) == '{"name":"","age":null,"other2":null}' + assert json.encode(JsonNullAttrFoo{ age: 10 }) == '{"name":null,"age":10,"other2":null}' + assert json.encode(JsonNullAttrFoo{ + age: 10 + other2: JsonNullAttrBar{ + name: none + } + }) == '{"name":null,"age":10,"other2":{"name":null}}' + assert json.decode[JsonNullAttrFoo](json.encode(JsonNullAttrFoo{}))! == JsonNullAttrFoo{} +} + fn test_skip_and_rename_attributes() { assert json.decode[StruWithJsonAttribute]('{"name": "hola1", "a": 2, "b": 3}')! == StruWithJsonAttribute{ a: 2 -- 2.39.5