diff --git a/pkg/types/price_volume_slice.go b/pkg/types/price_volume_slice.go index 95d1127fd..5ca022dbb 100644 --- a/pkg/types/price_volume_slice.go +++ b/pkg/types/price_volume_slice.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "encoding/json" "fmt" "sort" @@ -180,15 +181,40 @@ func (slice *PriceVolumeSlice) UnmarshalJSON(b []byte) error { return nil } +// ParsePriceVolumeKvSliceJSON parses a JSON array of objects into PriceVolumeSlice +// [{"Price":...,"Volume":...}, ...] +func ParsePriceVolumeKvSliceJSON(b []byte) (PriceVolumeSlice, error) { + type S PriceVolumeSlice + var ts S + + err := json.Unmarshal(b, &ts) + if err != nil { + return nil, err + } + + if len(ts) > 0 && ts[0].Price.IsZero() { + return nil, fmt.Errorf("unable to parse price volume slice correctly, input given: %s", string(b)) + } + + return PriceVolumeSlice(ts), nil +} + // ParsePriceVolumeSliceJSON tries to parse a 2 dimensional string array into a PriceVolumeSlice // // [["9000", "10"], ["9900", "10"], ... ] +// +// if parse failed, then it will try to parse the JSON array of objects, function ParsePriceVolumeKvSliceJSON will be called. func ParsePriceVolumeSliceJSON(b []byte) (slice PriceVolumeSlice, err error) { var as [][]fixedpoint.Value err = json.Unmarshal(b, &as) if err != nil { - return slice, err + // fallback unmarshalling: if the prefix looks like an object array + if bytes.HasPrefix(b, []byte(`[{`)) { + return ParsePriceVolumeKvSliceJSON(b) + } + + return nil, err } for _, a := range as { diff --git a/pkg/types/price_volume_slice_test.go b/pkg/types/price_volume_slice_test.go index cf0b1e8ab..05b9135d8 100644 --- a/pkg/types/price_volume_slice_test.go +++ b/pkg/types/price_volume_slice_test.go @@ -3,10 +3,33 @@ package types import ( "testing" - "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/fixedpoint" ) +func TestPriceVolumeSlice_UnmarshalJSON(t *testing.T) { + t.Run("array of array", func(t *testing.T) { + input := []byte(`[["19000.0","3.0"],["19111.0","2.0"]]`) + slice, err := ParsePriceVolumeSliceJSON(input) + if assert.NoError(t, err) { + assert.Len(t, slice, 2) + assert.Equal(t, "19000", slice[0].Price.String()) + assert.Equal(t, "3", slice[0].Volume.String()) + } + }) + + t.Run("array of object", func(t *testing.T) { + input := []byte(`[{ "Price": "19000.0", "Volume":"3.0"},{"Price": "19111.0","Volume": "2.0" }]`) + slice, err := ParsePriceVolumeSliceJSON(input) + if assert.NoError(t, err) { + assert.Len(t, slice, 2) + assert.Equal(t, "19000", slice[0].Price.String()) + assert.Equal(t, "3", slice[0].Volume.String()) + } + }) +} + func TestPriceVolumeSlice_Remove(t *testing.T) { for _, descending := range []bool{true, false} { slice := PriceVolumeSlice{}