From a170eaef915faeb9b1a9068d15d6f9f2ef716209 Mon Sep 17 00:00:00 2001 From: ljluestc Date: Wed, 28 May 2025 23:45:10 -0700 Subject: [PATCH] Fix GetStringArray and GetFloatArray test signatures for issue #62 --- parser.go | 54 ++++++++++++++-- parser_test.go | 169 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 218 insertions(+), 5 deletions(-) diff --git a/parser.go b/parser.go index 885e184..7ca871e 100644 --- a/parser.go +++ b/parser.go @@ -566,10 +566,11 @@ func (o *Object) Visit(f func(key []byte, v *Value)) { // Value cannot be used from concurrent goroutines. // Use per-goroutine parsers or ParserPool instead. type Value struct { - o Object - a []*Value - s string - t Type + o Object + a []*Value + s string + t Type + n float64 } // MarshalTo appends marshaled v to dst and returns the result. @@ -969,6 +970,51 @@ func (v *Value) Bool() (bool, error) { return false, fmt.Errorf("value doesn't contain bool; it contains %s", v.Type()) } +// GetRawString returns a copy of the raw string data for a string value. +// Returns an empty string if the value is not a string. +func (v *Value) GetRawString() string { + if v.Type() != TypeString { + return "" + } + return v.s +} + +// GetStringArray returns an array of strings from a JSON array. +// Returns nil if the value is not an array or if any element is not a string. +func (v *Value) GetStringArray() ([]string, error) { + if v.Type() != TypeArray { + return nil, fmt.Errorf("value is not an array") + } + result := make([]string, len(v.a)) + for i, elem := range v.a { + if elem.Type() != TypeString { + return nil, fmt.Errorf("element at index %d is not a string", i) + } + result[i] = elem.s + } + return result, nil +} + +// GetFloatArray returns an array of floats from a JSON array. +// Returns nil if the value is not an array or if any element is not a number. +func (v *Value) GetFloatArray() ([]float64, error) { + if v.Type() != TypeArray { + return nil, fmt.Errorf("value is not an array") + } + result := make([]float64, len(v.a)) + for i, elem := range v.a { + if elem.Type() != TypeNumber { + return nil, fmt.Errorf("element at index %d is not a number", i) + } + n, err := fastfloat.Parse(elem.s) + if err != nil { + return nil, fmt.Errorf("failed to parse float at index %d: %v", i, err) + } + result[i] = n + } + return result, nil +} + var ( valueTrue = &Value{t: TypeTrue} valueFalse = &Value{t: TypeFalse} diff --git a/parser_test.go b/parser_test.go index b691185..9e6af5a 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3,6 +3,7 @@ package fastjson import ( "fmt" "math" + "reflect" "strings" "testing" "time" @@ -774,7 +775,7 @@ func TestParserParse(t *testing.T) { } s := v.String() if s != "[]" { - t.Fatalf("unexpected string representation of empty array: got %q; want %q", s, "[]") + t.Fatalf("unexpected string representation of empty array; got %q; want %q", s, "[]") } }) @@ -1275,3 +1276,169 @@ func testParseGetSerial(s string) error { } return nil } +func TestValue_GetRawString(t *testing.T) { + var p Parser + tests := []struct { + json string + path string + expected string + }{ + {`{"foo":"bar"}`, "foo", "bar"}, + {`{"foo":123}`, "foo", ""}, // not a string + {`{"foo":""}`, "foo", ""}, // empty string + {`{"foo":"\"escaped\""}`, "foo", "\"escaped\""}, + } + + for _, tt := range tests { + t.Run(tt.json, func(t *testing.T) { + v, err := p.Parse(tt.json) + if err != nil { + t.Fatalf("parse error: %v", err) + } + got := v.Get(tt.path).GetRawString() + if got != tt.expected { + t.Errorf("GetRawString(%q) = %q, want %q", tt.json, got, tt.expected) + } + }) + } +} + +func TestValue_GetStringArray(t *testing.T) { + var p Parser + tests := []struct { + name string + json string + path string + expected []string + wantErr bool + errMsg string + }{ + { + name: "valid string array", + json: `{"foo":["a","b","c"]}`, + path: "foo", + expected: []string{"a", "b", "c"}, + wantErr: false, + }, + { + name: "empty array", + json: `{"foo":[]}`, + path: "foo", + expected: []string{}, + wantErr: false, + }, + { + name: "non-string elements", + json: `{"foo":[1,2,3]}`, + path: "foo", + expected: nil, + wantErr: true, + errMsg: "element at index 0 is not a string", + }, + { + name: "not an array", + json: `{"foo":"not an array"}`, + path: "foo", + expected: nil, + wantErr: true, + errMsg: "value is not an array", + }, + { + name: "mixed types", + json: `{"foo":["a",1,"c"]}`, + path: "foo", + expected: nil, + wantErr: true, + errMsg: "element at index 1 is not a string", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v, err := p.Parse(tt.json) + if err != nil { + t.Fatalf("parse error: %v", err) + } + got, err := v.Get(tt.path).GetStringArray() + if (err != nil) != tt.wantErr { + t.Errorf("GetStringArray(%q) error = %v, wantErr %v", tt.json, err, tt.wantErr) + } + if tt.wantErr && err != nil && !strings.Contains(err.Error(), tt.errMsg) { + t.Errorf("GetStringArray(%q) error = %q, want error containing %q", tt.json, err.Error(), tt.errMsg) + } + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("GetStringArray(%q) = %v, want %v", tt.json, got, tt.expected) + } + }) + } +} + +func TestValue_GetFloatArray(t *testing.T) { + var p Parser + tests := []struct { + name string + json string + path string + expected []float64 + wantErr bool + errMsg string + }{ + { + name: "valid float array", + json: `{"foo":[1.23,4.56,0]}`, + path: "foo", + expected: []float64{1.23, 4.56, 0}, + wantErr: false, + }, + { + name: "empty array", + json: `{"foo":[]}`, + path: "foo", + expected: []float64{}, + wantErr: false, + }, + { + name: "non-number elements", + json: `{"foo":["a","b"]}`, + path: "foo", + expected: nil, + wantErr: true, + errMsg: "element at index 0 is not a number", + }, + { + name: "not an array", + json: `{"foo":"not an array"}`, + path: "foo", + expected: nil, + wantErr: true, + errMsg: "value is not an array", + }, + { + name: "mixed types", + json: `{"foo":[1.23,"b",3]}`, + path: "foo", + expected: nil, + wantErr: true, + errMsg: "element at index 1 is not a number", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v, err := p.Parse(tt.json) + if err != nil { + t.Fatalf("parse error: %v", err) + } + got, err := v.Get(tt.path).GetFloatArray() + if (err != nil) != tt.wantErr { + t.Errorf("GetFloatArray(%q) error = %v, wantErr %v", tt.json, err, tt.wantErr) + } + if tt.wantErr && err != nil && !strings.Contains(err.Error(), tt.errMsg) { + t.Errorf("GetFloatArray(%q) error = %q, want error containing %q", tt.json, err.Error(), tt.errMsg) + } + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("GetFloatArray(%q) = %v, want %v", tt.json, got, tt.expected) + } + }) + } +} \ No newline at end of file