From 758d18513f5048780c135cd12077fcf1367c67d4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 18:23:20 +0000 Subject: [PATCH 1/2] =?UTF-8?q?json:=20Field=20v3=20migration=20=E2=80=94?= =?UTF-8?q?=20use=20Field.Name=20as=20key,=20OmitEmpty=20flag,=20post-deco?= =?UTF-8?q?de=20Validate()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements the Field v3 migration as described in docs/PLAN.md: - Updated `encode.go`: removed `parseJSONTag`, updated `encodeFielder` to use `field.Name` and `field.OmitEmpty`. - Updated `parser.go`: modified `matchFieldIndex` to use `field.Name` directly. - Updated `decode.go`: implemented `DecodeRaw` and updated `Decode` to include post-decode validation using `fmt.Validator`. - Updated all test files: removed `JSON` field literals, updated expectations, and added validation tests in `tests/validation_test.go`. - Updated `README.md` to reflect new API and behavior. Co-authored-by: cdvelop <44058491+cdvelop@users.noreply.github.com> --- README.md | 13 ++++-- decode.go | 16 ++++++- encode.go | 38 ++-------------- parser.go | 6 +-- tests/bench_encode_test.go | 10 ++--- tests/decode_basic_test.go | 18 ++++---- tests/decode_input_test.go | 4 +- tests/decode_nested_test.go | 2 +- tests/decode_tags_test.go | 38 +--------------- tests/decode_types_test.go | 18 ++++---- tests/encode_basic_test.go | 22 +++++----- tests/encode_output_test.go | 10 ++--- tests/encode_tags_test.go | 79 +++------------------------------- tests/encode_types_test.go | 2 +- tests/json_test.go | 8 ++-- tests/parser_bool_null_test.go | 2 +- tests/parser_limits_test.go | 2 +- tests/parser_number_test.go | 4 +- tests/parser_string_test.go | 4 +- tests/validation_test.go | 61 ++++++++++++++++++++++++++ 20 files changed, 151 insertions(+), 206 deletions(-) create mode 100644 tests/validation_test.go diff --git a/README.md b/README.md index a800ba3..da185de 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ type User struct { } func (u *User) Schema() []fmt.Field { - return []fmt.Field{{Name: "Name", Type: fmt.FieldText, JSON: "name"}} + return []fmt.Field{{Name: "name", Type: fmt.FieldText}} } func (u *User) Pointers() []any { return []any{&u.Name} } @@ -64,14 +64,21 @@ func main() { ### `Encode(data fmt.Fielder, output any) error` -Serializes a `Fielder` to JSON. +Serializes a `Fielder` to JSON. JSON keys are always taken from `field.Name`. If `field.OmitEmpty` is true, the field is skipped if its value is zero. - **data**: Must implement `fmt.Fielder`. - **output**: `*[]byte`, `*string`, or `io.Writer`. ### `Decode(input any, data fmt.Fielder) error` -Parses JSON into a `Fielder`. +Parses JSON into a `Fielder` and calls `Validate()` if the fielder implements `fmt.Validator`. + +- **input**: `[]byte`, `string`, or `io.Reader`. +- **data**: Must implement `fmt.Fielder`. + +### `DecodeRaw(input any, data fmt.Fielder) error` + +Parses JSON into a `Fielder` without calling `Validate()`. - **input**: `[]byte`, `string`, or `io.Reader`. - **data**: Must implement `fmt.Fielder`. diff --git a/decode.go b/decode.go index 28622c8..4057837 100644 --- a/decode.go +++ b/decode.go @@ -6,9 +6,23 @@ import ( "unsafe" ) -// Decode parses JSON into a Fielder. +// Decode parses JSON into a Fielder and calls Validate() if implemented. // input: []byte | string | io.Reader. func Decode(input any, data fmt.Fielder) error { + if err := DecodeRaw(input, data); err != nil { + return err + } + + // Post-decode validation + if v, ok := data.(fmt.Validator); ok { + return v.Validate() + } + return nil +} + +// DecodeRaw parses JSON into a Fielder without calling Validate(). +// input: []byte | string | io.Reader. +func DecodeRaw(input any, data fmt.Fielder) error { var raw []byte switch in := input.(type) { case []byte: diff --git a/encode.go b/encode.go index 0bac5d6..90f8e20 100644 --- a/encode.go +++ b/encode.go @@ -46,14 +46,7 @@ func encodeFielder(b *fmt.Conv, f fmt.Fielder) error { first := true for i, field := range schema { - key, omitempty := parseJSONTag(field) - if key == "-" { - continue - } - - ptr := ptrs[i] - - if omitempty && isZeroPtr(ptr, field.Type) { + if field.OmitEmpty && isZeroPtr(ptrs[i], field.Type) { continue } @@ -63,11 +56,11 @@ func encodeFielder(b *fmt.Conv, f fmt.Fielder) error { first = false b.WriteByte('"') - fmt.JSONEscape(key, b) + fmt.JSONEscape(field.Name, b) b.WriteByte('"') b.WriteByte(':') - encodeFromPtr(b, ptr, field.Type) + encodeFromPtr(b, ptrs[i], field.Type) } b.WriteByte('}') @@ -196,28 +189,3 @@ func isZeroPtr(ptr any, ft fmt.FieldType) bool { return false } -// parseJSONTag extracts key and omitempty from Field.JSON. -func parseJSONTag(f fmt.Field) (key string, omitempty bool) { - tag := f.JSON - if tag == "" { - return f.Name, false - } - if tag == "-" { - return "-", false - } - comma := -1 - for i := 0; i < len(tag); i++ { - if tag[i] == ',' { - comma = i - break - } - } - if comma < 0 { - return tag, false - } - key = tag[:comma] - if key == "" { - key = f.Name - } - return key, tag[comma+1:] == "omitempty" -} diff --git a/parser.go b/parser.go index 30b810b..277aab7 100644 --- a/parser.go +++ b/parser.go @@ -349,8 +349,7 @@ func (p *parser) matchFieldIndex(schema []fmt.Field) (int, error) { keyBytes := p.data[start:p.pos] p.pos++ // consume closing '"' for i, field := range schema { - k, _ := parseJSONTag(field) - if len(k) == len(keyBytes) && matchBytesStr(k, keyBytes) { + if len(field.Name) == len(keyBytes) && matchBytesStr(field.Name, keyBytes) { return i, nil } } @@ -363,8 +362,7 @@ func (p *parser) matchFieldIndex(schema []fmt.Field) (int, error) { return -1, err } for i, field := range schema { - k, _ := parseJSONTag(field) - if k == key { + if field.Name == key { return i, nil } } diff --git a/tests/bench_encode_test.go b/tests/bench_encode_test.go index d760696..f7abf23 100644 --- a/tests/bench_encode_test.go +++ b/tests/bench_encode_test.go @@ -15,16 +15,16 @@ type benchUser struct { } var benchSchema = []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, - {Name: "Email", Type: fmt.FieldText, JSON: "email"}, - {Name: "Age", Type: fmt.FieldInt, JSON: "age"}, - {Name: "Score", Type: fmt.FieldFloat, JSON: "score"}, + {Name: "name", Type: fmt.FieldText}, + {Name: "email", Type: fmt.FieldText}, + {Name: "age", Type: fmt.FieldInt}, + {Name: "score", Type: fmt.FieldFloat}, } func (u *benchUser) Schema() []fmt.Field { return benchSchema } func (u *benchUser) Pointers() []any { return []any{&u.Name, &u.Email, &u.Age, &u.Score} } -var benchInput = &benchUser{Name: "Alice", Email: "alice@example.com", Age: 30, Score: 9.5} +var benchInput = &benchUser{Name: "alice", Email: "alice@example.com", Age: 30, Score: 9.5} func BenchmarkEncode_tinywasm(b *testing.B) { var out string diff --git a/tests/decode_basic_test.go b/tests/decode_basic_test.go index 7027e84..6976662 100644 --- a/tests/decode_basic_test.go +++ b/tests/decode_basic_test.go @@ -12,9 +12,9 @@ func TestDecodeSimple(t *testing.T) { var active bool m := &mockFielder{ schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, - {Name: "Age", Type: fmt.FieldInt, JSON: "age"}, - {Name: "Active", Type: fmt.FieldBool, JSON: "active"}, + {Name: "name", Type: fmt.FieldText}, + {Name: "age", Type: fmt.FieldInt}, + {Name: "active", Type: fmt.FieldBool}, }, pointers: []any{&name, &age, &active}, } @@ -31,15 +31,15 @@ func TestDecodeNested(t *testing.T) { var city string inner := &mockFielder{ schema: []fmt.Field{ - {Name: "City", Type: fmt.FieldText, JSON: "city"}, + {Name: "city", Type: fmt.FieldText}, }, pointers: []any{&city}, } var user string outer := &mockFielder{ schema: []fmt.Field{ - {Name: "User", Type: fmt.FieldText, JSON: "user"}, - {Name: "Address", Type: fmt.FieldStruct, JSON: "address"}, + {Name: "user", Type: fmt.FieldText}, + {Name: "address", Type: fmt.FieldStruct}, }, pointers: []any{&user, inner}, } @@ -56,7 +56,7 @@ func TestDecodeStringEscapes(t *testing.T) { var msg string m := &mockFielder{ schema: []fmt.Field{ - {Name: "Msg", Type: fmt.FieldText, JSON: "msg"}, + {Name: "msg", Type: fmt.FieldText}, }, pointers: []any{&msg}, } @@ -74,7 +74,7 @@ func TestDecodeNull(t *testing.T) { name := "Alice" m := &mockFielder{ schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, + {Name: "name", Type: fmt.FieldText}, }, pointers: []any{&name}, } @@ -91,7 +91,7 @@ func TestDecodeBytes(t *testing.T) { var data []byte m := &mockFielder{ schema: []fmt.Field{ - {Name: "Data", Type: fmt.FieldBlob, JSON: "data"}, + {Name: "data", Type: fmt.FieldBlob}, }, pointers: []any{&data}, } diff --git a/tests/decode_input_test.go b/tests/decode_input_test.go index f920d8d..4688cf5 100644 --- a/tests/decode_input_test.go +++ b/tests/decode_input_test.go @@ -11,7 +11,7 @@ func TestDecodeFromReader(t *testing.T) { var name string m := &mockFielder{ schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, + {Name: "name", Type: fmt.FieldText}, }, pointers: []any{&name}, } @@ -53,7 +53,7 @@ func TestDecodeFromBytes(t *testing.T) { var name string m := &mockFielder{ schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, + {Name: "name", Type: fmt.FieldText}, }, pointers: []any{&name}, } diff --git a/tests/decode_nested_test.go b/tests/decode_nested_test.go index 3d1065e..154cc24 100644 --- a/tests/decode_nested_test.go +++ b/tests/decode_nested_test.go @@ -11,7 +11,7 @@ func TestDecodeStructNotFielder(t *testing.T) { var s string = "initial" m := &mockFielder{ schema: []fmt.Field{ - {Name: "Nested", Type: fmt.FieldStruct, JSON: "nested"}, + {Name: "nested", Type: fmt.FieldStruct}, }, pointers: []any{&s}, // string doesn't implement Fielder } diff --git a/tests/decode_tags_test.go b/tests/decode_tags_test.go index da62c1a..1873813 100644 --- a/tests/decode_tags_test.go +++ b/tests/decode_tags_test.go @@ -6,45 +6,11 @@ import ( "testing" ) -func TestDecodeJSONKey(t *testing.T) { - var firstName string - m := &mockFielder{ - schema: []fmt.Field{ - {Name: "FirstName", Type: fmt.FieldText, JSON: "first_name"}, - }, - pointers: []any{&firstName}, - } - input := `{"first_name":"Alice"}` - if err := json.Decode(input, m); err != nil { - t.Fatal(err) - } - if firstName != "Alice" { - t.Errorf("got %s", firstName) - } -} - -func TestDecodeJSONExclude(t *testing.T) { - secret := "initial" - m := &mockFielder{ - schema: []fmt.Field{ - {Name: "Secret", Type: fmt.FieldText, JSON: "-"}, - }, - pointers: []any{&secret}, - } - input := `{"Secret":"new"}` - if err := json.Decode(input, m); err != nil { - t.Fatal(err) - } - if secret != "initial" { - t.Errorf("secret changed to %s", secret) - } -} - func TestDecodeMissingField(t *testing.T) { age := int64(20) m := &mockFielder{ schema: []fmt.Field{ - {Name: "Age", Type: fmt.FieldInt, JSON: "age"}, + {Name: "age", Type: fmt.FieldInt}, }, pointers: []any{&age}, } @@ -61,7 +27,7 @@ func TestDecodeExtraField(t *testing.T) { var name string m := &mockFielder{ schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, + {Name: "name", Type: fmt.FieldText}, }, pointers: []any{&name}, } diff --git a/tests/decode_types_test.go b/tests/decode_types_test.go index f656b9b..3f3104a 100644 --- a/tests/decode_types_test.go +++ b/tests/decode_types_test.go @@ -10,7 +10,7 @@ func TestDecodeFloatFromInt(t *testing.T) { var price float64 m := &mockFielder{ schema: []fmt.Field{ - {Name: "Price", Type: fmt.FieldFloat, JSON: "price"}, + {Name: "price", Type: fmt.FieldFloat}, }, pointers: []any{&price}, } @@ -27,7 +27,7 @@ func TestDecodeFloatFromInt(t *testing.T) { func TestDecodeInt(t *testing.T) { var v int m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldInt, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldInt}}, pointers: []any{&v}, } if err := json.Decode(`{"v":42}`, m); err != nil { @@ -42,7 +42,7 @@ func TestDecodeInt(t *testing.T) { func TestDecodeInt32(t *testing.T) { var v int32 m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldInt, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldInt}}, pointers: []any{&v}, } if err := json.Decode(`{"v":42}`, m); err != nil { @@ -57,7 +57,7 @@ func TestDecodeInt32(t *testing.T) { func TestDecodeFloat32(t *testing.T) { var v float32 m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldFloat, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldFloat}}, pointers: []any{&v}, } if err := json.Decode(`{"v":1.5}`, m); err != nil { @@ -72,7 +72,7 @@ func TestDecodeFloat32(t *testing.T) { func TestDecodeInt32FromFloat(t *testing.T) { var v int32 m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldInt, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldInt}}, pointers: []any{&v}, } if err := json.Decode(`{"v":42.0}`, m); err != nil { @@ -87,7 +87,7 @@ func TestDecodeInt32FromFloat(t *testing.T) { func TestDecodeIntFromFloat(t *testing.T) { var v int m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldInt, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldInt}}, pointers: []any{&v}, } if err := json.Decode(`{"v":42.0}`, m); err != nil { @@ -102,7 +102,7 @@ func TestDecodeIntFromFloat(t *testing.T) { func TestDecodeFloat32FromInt(t *testing.T) { var v float32 m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldFloat, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldFloat}}, pointers: []any{&v}, } if err := json.Decode(`{"v":42}`, m); err != nil { @@ -117,7 +117,7 @@ func TestDecodeFloat32FromInt(t *testing.T) { func TestDecodeInt64Ptr(t *testing.T) { var v int64 m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldInt, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldInt}}, pointers: []any{&v}, } if err := json.Decode(`{"v":42}`, m); err != nil { @@ -132,7 +132,7 @@ func TestDecodeInt64Ptr(t *testing.T) { func TestDecodeInt64FromFloat(t *testing.T) { var v int64 m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldInt, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldInt}}, pointers: []any{&v}, } if err := json.Decode(`{"v":42.0}`, m); err != nil { diff --git a/tests/encode_basic_test.go b/tests/encode_basic_test.go index 0541763..db23fb1 100644 --- a/tests/encode_basic_test.go +++ b/tests/encode_basic_test.go @@ -9,9 +9,9 @@ import ( func TestEncodeSimple(t *testing.T) { m := &mockFielder{ schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, - {Name: "Age", Type: fmt.FieldInt, JSON: "age"}, - {Name: "Active", Type: fmt.FieldBool, JSON: "active"}, + {Name: "name", Type: fmt.FieldText}, + {Name: "age", Type: fmt.FieldInt}, + {Name: "active", Type: fmt.FieldBool}, }, pointers: []any{ptrString("Alice"), ptrInt64(30), ptrBool(true)}, } @@ -27,12 +27,12 @@ func TestEncodeSimple(t *testing.T) { func TestEncodeFielderError(t *testing.T) { inner := &mockFielder{ - schema: []fmt.Field{{Name: "E", Type: fmt.FieldText, JSON: "e"}}, + schema: []fmt.Field{{Name: "e", Type: fmt.FieldText}}, pointers: []any{nil}, err: fmt.Err("test", "encode", "error"), } outer := &mockFielder{ - schema: []fmt.Field{{Name: "I", Type: fmt.FieldStruct, JSON: "i"}}, + schema: []fmt.Field{{Name: "i", Type: fmt.FieldStruct}}, pointers: []any{inner}, } var out string @@ -46,7 +46,7 @@ func TestEncodeFieldBytesNonBytes(t *testing.T) { // Actually encodeValue handles it via default (fmt.Convert). // To trigger default in encodeValue with something that is NOT handled by other cases: m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldBlob, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldBlob}}, pointers: []any{ptrInt(42)}, // Not []byte, not string, not bool, not nil } var out string @@ -67,7 +67,7 @@ func TestEncodeFieldBytesNonBytes(t *testing.T) { func TestEncodeStringEscaping(t *testing.T) { m := &mockFielder{ schema: []fmt.Field{ - {Name: "Msg", Type: fmt.FieldText, JSON: "msg"}, + {Name: "msg", Type: fmt.FieldText}, }, pointers: []any{ptrString("hello \"world\"\n\r\t\\")}, } @@ -84,7 +84,7 @@ func TestEncodeStringEscaping(t *testing.T) { func TestEncodeNilField(t *testing.T) { m := &mockFielder{ schema: []fmt.Field{ - {Name: "Val", Type: fmt.FieldText, JSON: "val"}, + {Name: "val", Type: fmt.FieldText}, }, pointers: []any{nil}, } @@ -101,7 +101,7 @@ func TestEncodeNilField(t *testing.T) { func TestEncodeBytes(t *testing.T) { m := &mockFielder{ schema: []fmt.Field{ - {Name: "Data", Type: fmt.FieldBlob, JSON: "data"}, + {Name: "data", Type: fmt.FieldBlob}, }, pointers: []any{ptrBytes([]byte("hello"))}, } @@ -119,7 +119,7 @@ func TestEncodeBytes(t *testing.T) { func TestEncodeStructNotFielder(t *testing.T) { m := &mockFielder{ schema: []fmt.Field{ - {Name: "User", Type: fmt.FieldStruct, JSON: "user"}, + {Name: "user", Type: fmt.FieldStruct}, }, pointers: []any{ptrString("not-a-fielder")}, } @@ -137,7 +137,7 @@ func TestEncodeStructNotFielder(t *testing.T) { func TestEncodeControlChars(t *testing.T) { m := &mockFielder{ schema: []fmt.Field{ - {Name: "Msg", Type: fmt.FieldText, JSON: "msg"}, + {Name: "msg", Type: fmt.FieldText}, }, pointers: []any{ptrString("\x01\x1f")}, } diff --git a/tests/encode_output_test.go b/tests/encode_output_test.go index 978ac92..2534ba0 100644 --- a/tests/encode_output_test.go +++ b/tests/encode_output_test.go @@ -9,7 +9,7 @@ import ( func TestEncodeToBytes(t *testing.T) { m := &mockFielder{ - schema: []fmt.Field{{Name: "A", Type: fmt.FieldInt, JSON: "a"}}, + schema: []fmt.Field{{Name: "a", Type: fmt.FieldInt}}, pointers: []any{ptrInt64(1)}, } var out []byte @@ -24,7 +24,7 @@ func TestEncodeToBytes(t *testing.T) { func TestEncodeToWriter(t *testing.T) { m := &mockFielder{ - schema: []fmt.Field{{Name: "A", Type: fmt.FieldInt, JSON: "a"}}, + schema: []fmt.Field{{Name: "a", Type: fmt.FieldInt}}, pointers: []any{ptrInt64(1)}, } var buf bytes.Buffer @@ -40,7 +40,7 @@ func TestEncodeToWriter(t *testing.T) { // TestEncodeToString — output *string (ausente en tests originales) func TestEncodeToString(t *testing.T) { m := &mockFielder{ - schema: []fmt.Field{{Name: "A", Type: fmt.FieldInt, JSON: "a"}}, + schema: []fmt.Field{{Name: "a", Type: fmt.FieldInt}}, pointers: []any{ptrInt64(1)}, } var out string @@ -56,7 +56,7 @@ func TestEncodeToString(t *testing.T) { // TestEncodeInvalidOutput — output desconocido → error func TestEncodeInvalidOutput(t *testing.T) { m := &mockFielder{ - schema: []fmt.Field{{Name: "A", Type: fmt.FieldInt, JSON: "a"}}, + schema: []fmt.Field{{Name: "a", Type: fmt.FieldInt}}, pointers: []any{ptrInt64(1)}, } if err := json.Encode(m, 123); err == nil { @@ -72,7 +72,7 @@ func (e *errWriter) Write(p []byte) (n int, err error) { func TestEncodeWriterError(t *testing.T) { m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: fmt.FieldText, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: fmt.FieldText}}, pointers: []any{ptrString("hello")}, } if err := json.Encode(m, &errWriter{}); err == nil { diff --git a/tests/encode_tags_test.go b/tests/encode_tags_test.go index a28bd6d..e958019 100644 --- a/tests/encode_tags_test.go +++ b/tests/encode_tags_test.go @@ -9,14 +9,14 @@ import ( func TestEncodeNested(t *testing.T) { inner := &mockFielder{ schema: []fmt.Field{ - {Name: "City", Type: fmt.FieldText, JSON: "city"}, + {Name: "city", Type: fmt.FieldText}, }, pointers: []any{ptrString("Paris")}, } outer := &mockFielder{ schema: []fmt.Field{ - {Name: "User", Type: fmt.FieldText, JSON: "user"}, - {Name: "Address", Type: fmt.FieldStruct, JSON: "address"}, + {Name: "user", Type: fmt.FieldText}, + {Name: "address", Type: fmt.FieldStruct}, }, pointers: []any{ptrString("Alice"), inner}, } @@ -30,28 +30,11 @@ func TestEncodeNested(t *testing.T) { } } -func TestEncodeJSONKeyOmitEmpty(t *testing.T) { - m := &mockFielder{ - schema: []fmt.Field{ - {Name: "FirstName", Type: fmt.FieldText, JSON: ",omitempty"}, - }, - pointers: []any{ptrString("")}, - } - var out string - if err := json.Encode(m, &out); err != nil { - t.Fatal(err) - } - expected := `{}` - if out != expected { - t.Errorf("expected %s, got %s", expected, out) - } -} - func TestEncodeOmitEmpty(t *testing.T) { m := &mockFielder{ schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, - {Name: "Age", Type: fmt.FieldInt, JSON: "age,omitempty"}, + {Name: "name", Type: fmt.FieldText}, + {Name: "age", Type: fmt.FieldInt, OmitEmpty: true}, }, pointers: []any{ptrString("Alice"), ptrInt64(0)}, } @@ -64,55 +47,3 @@ func TestEncodeOmitEmpty(t *testing.T) { t.Errorf("expected %s, got %s", expected, out) } } - -func TestEncodeJSONExclude(t *testing.T) { - m := &mockFielder{ - schema: []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, - {Name: "Secret", Type: fmt.FieldText, JSON: "-"}, - }, - pointers: []any{ptrString("Alice"), ptrString("shhh")}, - } - var out string - if err := json.Encode(m, &out); err != nil { - t.Fatal(err) - } - expected := `{"name":"Alice"}` - if out != expected { - t.Errorf("expected %s, got %s", expected, out) - } -} - -func TestEncodeJSONKey(t *testing.T) { - m := &mockFielder{ - schema: []fmt.Field{ - {Name: "FirstName", Type: fmt.FieldText, JSON: "first_name"}, - }, - pointers: []any{ptrString("Alice")}, - } - var out string - if err := json.Encode(m, &out); err != nil { - t.Fatal(err) - } - expected := `{"first_name":"Alice"}` - if out != expected { - t.Errorf("expected %s, got %s", expected, out) - } -} - -func TestEncodeJSONKeyFallback(t *testing.T) { - m := &mockFielder{ - schema: []fmt.Field{ - {Name: "FirstName", Type: fmt.FieldText, JSON: ""}, - }, - pointers: []any{ptrString("Alice")}, - } - var out string - if err := json.Encode(m, &out); err != nil { - t.Fatal(err) - } - expected := `{"FirstName":"Alice"}` - if out != expected { - t.Errorf("expected %s, got %s", expected, out) - } -} diff --git a/tests/encode_types_test.go b/tests/encode_types_test.go index dce2afb..167c985 100644 --- a/tests/encode_types_test.go +++ b/tests/encode_types_test.go @@ -25,7 +25,7 @@ func TestEncodeNumericTypes(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { m := &mockFielder{ - schema: []fmt.Field{{Name: "V", Type: c.ft, JSON: "v"}}, + schema: []fmt.Field{{Name: "v", Type: c.ft}}, pointers: []any{c.ptr}, } var out string diff --git a/tests/json_test.go b/tests/json_test.go index 0c5c1dc..1142bf0 100644 --- a/tests/json_test.go +++ b/tests/json_test.go @@ -13,8 +13,8 @@ type TestStruct struct { func (s *TestStruct) Schema() []fmt.Field { return []fmt.Field{ - {Name: "Name", Type: fmt.FieldText, JSON: "name"}, - {Name: "Age", Type: fmt.FieldInt, JSON: "age"}, + {Name: "name", Type: fmt.FieldText}, + {Name: "age", Type: fmt.FieldInt}, } } @@ -24,13 +24,13 @@ func (s *TestStruct) Pointers() []any { func TestIntegration(t *testing.T) { t.Run("Encode Fielder", func(t *testing.T) { - input := &TestStruct{Name: "Alice", Age: 30} + input := &TestStruct{Name: "alice", Age: 30} var result string err := json.Encode(input, &result) if err != nil { t.Fatalf("Encode failed: %v", err) } - expected := `{"name":"Alice","age":30}` + expected := `{"name":"alice","age":30}` if result != expected { t.Errorf("Expected %s, got %s", expected, result) } diff --git a/tests/parser_bool_null_test.go b/tests/parser_bool_null_test.go index 0025ec1..2e13c9b 100644 --- a/tests/parser_bool_null_test.go +++ b/tests/parser_bool_null_test.go @@ -9,7 +9,7 @@ import ( func TestParseBoolFalse(t *testing.T) { var b bool = true m := &mockFielder{ - schema: []fmt.Field{{Name: "B", Type: fmt.FieldBool, JSON: "b"}}, + schema: []fmt.Field{{Name: "b", Type: fmt.FieldBool}}, pointers: []any{&b}, } if err := json.Decode(`{"b":false}`, m); err != nil { diff --git a/tests/parser_limits_test.go b/tests/parser_limits_test.go index a91ff43..df9a625 100644 --- a/tests/parser_limits_test.go +++ b/tests/parser_limits_test.go @@ -16,7 +16,7 @@ func TestParseIntoFielderNotObject(t *testing.T) { func TestSkipWhitespace(t *testing.T) { var n int64 m := &mockFielder{ - schema: []fmt.Field{{Name: "N", Type: fmt.FieldInt, JSON: "n"}}, + schema: []fmt.Field{{Name: "n", Type: fmt.FieldInt}}, pointers: []any{&n}, } input := " \t\r\n{ \t\r\n\"n\" \t\r\n: \t\r\n42 \t\r\n} \t\r\n" diff --git a/tests/parser_number_test.go b/tests/parser_number_test.go index b28406b..d42c2d7 100644 --- a/tests/parser_number_test.go +++ b/tests/parser_number_test.go @@ -9,7 +9,7 @@ import ( func TestParseNumberNegative(t *testing.T) { var n int64 m := &mockFielder{ - schema: []fmt.Field{{Name: "N", Type: fmt.FieldInt, JSON: "n"}}, + schema: []fmt.Field{{Name: "n", Type: fmt.FieldInt}}, pointers: []any{&n}, } input := `{"n":-42}` @@ -24,7 +24,7 @@ func TestParseNumberNegative(t *testing.T) { func TestParseNumberScientific(t *testing.T) { var f float64 m := &mockFielder{ - schema: []fmt.Field{{Name: "F", Type: fmt.FieldFloat, JSON: "f"}}, + schema: []fmt.Field{{Name: "f", Type: fmt.FieldFloat}}, pointers: []any{&f}, } input := `{"f":1e2}` diff --git a/tests/parser_string_test.go b/tests/parser_string_test.go index a6ea32a..2c4c261 100644 --- a/tests/parser_string_test.go +++ b/tests/parser_string_test.go @@ -9,7 +9,7 @@ import ( func TestParseStringEscapeBF(t *testing.T) { var s string m := &mockFielder{ - schema: []fmt.Field{{Name: "S", Type: fmt.FieldText, JSON: "s"}}, + schema: []fmt.Field{{Name: "s", Type: fmt.FieldText}}, pointers: []any{&s}, } input := `{"s":"\b\f\/"}` @@ -25,7 +25,7 @@ func TestParseStringEscapeBF(t *testing.T) { func TestParseStringUnicode(t *testing.T) { var s string m := &mockFielder{ - schema: []fmt.Field{{Name: "S", Type: fmt.FieldText, JSON: "s"}}, + schema: []fmt.Field{{Name: "s", Type: fmt.FieldText}}, pointers: []any{&s}, } input := `{"s":"\u0041"}` diff --git a/tests/validation_test.go b/tests/validation_test.go new file mode 100644 index 0000000..a40fc44 --- /dev/null +++ b/tests/validation_test.go @@ -0,0 +1,61 @@ +package tests + +import ( + "github.com/tinywasm/json" + "github.com/tinywasm/fmt" + "testing" +) + +type validatingFielder struct { + mockFielder + valid bool +} + +func (v *validatingFielder) Validate() error { + if !v.valid { + return fmt.Err("validation", "failed") + } + return nil +} + +func TestDecodeValidation(t *testing.T) { + var name string + m := &validatingFielder{ + mockFielder: mockFielder{ + schema: []fmt.Field{{Name: "name", Type: fmt.FieldText}}, + pointers: []any{&name}, + }, + valid: false, + } + + input := `{"name":"Alice"}` + err := json.Decode(input, m) + if err == nil { + t.Fatal("expected validation error") + } + if name != "Alice" { + t.Errorf("expected name to be Alice, got %s", name) + } + + m.valid = true + err = json.Decode(input, m) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestDecodeRawNoValidation(t *testing.T) { + m := &validatingFielder{ + mockFielder: mockFielder{ + schema: []fmt.Field{{Name: "name", Type: fmt.FieldText}}, + pointers: []any{new(string)}, + }, + valid: false, + } + + input := `{"name":"Alice"}` + err := json.DecodeRaw(input, m) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} From 5633b6f0f8423f34fbc56085c6f17a3d04ed01f2 Mon Sep 17 00:00:00 2001 From: Cesar Solis <44058491+cdvelop@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:42:44 -0300 Subject: [PATCH 2/2] review: corrections before merge --- docs/{PLAN.md => CHECK_PLAN.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{PLAN.md => CHECK_PLAN.md} (100%) diff --git a/docs/PLAN.md b/docs/CHECK_PLAN.md similarity index 100% rename from docs/PLAN.md rename to docs/CHECK_PLAN.md