diff --git a/plugins/parsers/json/parser.go b/plugins/parsers/json/parser.go index 6df5d9196..7606b7629 100644 --- a/plugins/parsers/json/parser.go +++ b/plugins/parsers/json/parser.go @@ -3,6 +3,7 @@ package json import ( "bytes" "encoding/json" + "errors" "fmt" "log" "strconv" @@ -17,7 +18,8 @@ import ( ) var ( - utf8BOM = []byte("\xef\xbb\xbf") + utf8BOM = []byte("\xef\xbb\xbf") + ErrWrongType = errors.New("must be an object or an array of objects") ) type Config struct { @@ -63,32 +65,34 @@ func New(config *Config) (*Parser, error) { }, nil } -func (p *Parser) parseArray(buf []byte) ([]telegraf.Metric, error) { - metrics := make([]telegraf.Metric, 0) +func (p *Parser) parseArray(data []interface{}) ([]telegraf.Metric, error) { + results := make([]telegraf.Metric, 0) + + for _, item := range data { + switch v := item.(type) { + case map[string]interface{}: + metrics, err := p.parseObject(v) + if err != nil { + return nil, err + } + results = append(results, metrics...) + default: + return nil, ErrWrongType - var jsonOut []map[string]interface{} - err := json.Unmarshal(buf, &jsonOut) - if err != nil { - err = fmt.Errorf("unable to parse out as JSON Array, %s", err) - return nil, err - } - for _, item := range jsonOut { - metrics, err = p.parseObject(metrics, item) - if err != nil { - return nil, err } } - return metrics, nil + + return results, nil } -func (p *Parser) parseObject(metrics []telegraf.Metric, jsonOut map[string]interface{}) ([]telegraf.Metric, error) { +func (p *Parser) parseObject(data map[string]interface{}) ([]telegraf.Metric, error) { tags := make(map[string]string) for k, v := range p.defaultTags { tags[k] = v } f := JSONFlattener{} - err := f.FullFlattenJSON("", jsonOut, true, true) + err := f.FullFlattenJSON("", data, true, true) if err != nil { return nil, err } @@ -134,7 +138,7 @@ func (p *Parser) parseObject(metrics []telegraf.Metric, jsonOut map[string]inter if err != nil { return nil, err } - return append(metrics, metric), nil + return []telegraf.Metric{metric}, nil } //will take in field map with strings and bools, @@ -193,17 +197,20 @@ func (p *Parser) Parse(buf []byte) ([]telegraf.Metric, error) { return make([]telegraf.Metric, 0), nil } - if !isarray(buf) { - metrics := make([]telegraf.Metric, 0) - var jsonOut map[string]interface{} - err := json.Unmarshal(buf, &jsonOut) - if err != nil { - err = fmt.Errorf("unable to parse out as JSON, %s", err) - return nil, err - } - return p.parseObject(metrics, jsonOut) + var data interface{} + err := json.Unmarshal(buf, &data) + if err != nil { + return nil, err + } + + switch v := data.(type) { + case map[string]interface{}: + return p.parseObject(v) + case []interface{}: + return p.parseArray(v) + default: + return nil, ErrWrongType } - return p.parseArray(buf) } func (p *Parser) ParseLine(line string) (telegraf.Metric, error) { @@ -288,13 +295,3 @@ func (f *JSONFlattener) FullFlattenJSON( } return nil } - -func isarray(buf []byte) bool { - ia := bytes.IndexByte(buf, '[') - ib := bytes.IndexByte(buf, '{') - if ia > -1 && ia < ib { - return true - } else { - return false - } -} diff --git a/plugins/parsers/json/parser_test.go b/plugins/parsers/json/parser_test.go index f656a96a1..44ae73af5 100644 --- a/plugins/parsers/json/parser_test.go +++ b/plugins/parsers/json/parser_test.go @@ -2,7 +2,6 @@ package json import ( "fmt" - "log" "testing" "time" @@ -496,7 +495,7 @@ func TestJSONParseNestedArray(t *testing.T) { require.NoError(t, err) metrics, err := parser.Parse([]byte(testString)) - log.Printf("m[0] name: %v, tags: %v, fields: %v", metrics[0].Name(), metrics[0].Tags(), metrics[0].Fields()) + require.Len(t, metrics, 1) require.NoError(t, err) require.Equal(t, 3, len(metrics[0].Tags())) } @@ -754,7 +753,6 @@ func TestTimeErrors(t *testing.T) { require.NoError(t, err) metrics, err = parser.Parse([]byte(testString2)) - log.Printf("err: %v", err) require.Error(t, err) require.Equal(t, 0, len(metrics)) require.Equal(t, fmt.Errorf("JSON time key could not be found"), err) @@ -840,3 +838,51 @@ func TestStringFieldGlob(t *testing.T) { testutil.RequireMetricsEqual(t, expected, actual) } + +func TestParseEmptyArray(t *testing.T) { + data := `[]` + + parser, err := New(&Config{}) + require.NoError(t, err) + + actual, err := parser.Parse([]byte(data)) + require.NoError(t, err) + + expected := []telegraf.Metric{} + testutil.RequireMetricsEqual(t, expected, actual) +} + +func TestParseSimpleArray(t *testing.T) { + data := `[{"answer": 42}]` + + parser, err := New(&Config{ + MetricName: "json", + }) + require.NoError(t, err) + + actual, err := parser.Parse([]byte(data)) + require.NoError(t, err) + + expected := []telegraf.Metric{ + testutil.MustMetric( + "json", + map[string]string{}, + map[string]interface{}{ + "answer": 42.0, + }, + time.Unix(0, 0), + ), + } + + testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) +} + +func TestParseArrayWithWrongType(t *testing.T) { + data := `[{"answer": 42}, 123]` + + parser, err := New(&Config{}) + require.NoError(t, err) + + _, err = parser.Parse([]byte(data)) + require.Error(t, err) +}