Skip to content

Commit 8a44852

Browse files
feat: implementing OptimizelyJSON (#250)
* feat: implementing OptimizelyJSON
1 parent d679dd8 commit 8a44852

File tree

2 files changed

+354
-0
lines changed

2 files changed

+354
-0
lines changed

pkg/optimizelyjson/optimizely_json.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/****************************************************************************
2+
* Copyright 2020, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
// Package optimizelyjson //
18+
package optimizelyjson
19+
20+
import (
21+
"encoding/json"
22+
"errors"
23+
"fmt"
24+
"strings"
25+
)
26+
27+
// OptimizelyJSON holds the underlying structure of the object
28+
type OptimizelyJSON struct {
29+
payload string
30+
31+
data map[string]interface{}
32+
}
33+
34+
// NewOptimizelyJSONfromString constructs the object out of string payload
35+
func NewOptimizelyJSONfromString(payload string) *OptimizelyJSON {
36+
return &OptimizelyJSON{payload: payload}
37+
}
38+
39+
// NewOptimizelyJSONfromMap constructs the object
40+
func NewOptimizelyJSONfromMap(data map[string]interface{}) *OptimizelyJSON {
41+
return &OptimizelyJSON{data: data}
42+
}
43+
44+
// ToString returns the string representation of json
45+
func (optlyJson *OptimizelyJSON) ToString() (string, error) {
46+
if optlyJson.payload == "" {
47+
jsonBytes, err := json.Marshal(optlyJson.data)
48+
if err != nil {
49+
return "", err
50+
}
51+
optlyJson.payload = string(jsonBytes)
52+
53+
}
54+
return optlyJson.payload, nil
55+
}
56+
57+
// ToMap returns the native representation of json (map of interface)
58+
func (optlyJson *OptimizelyJSON) ToMap() (map[string]interface{}, error) {
59+
var err error
60+
if optlyJson.data == nil {
61+
err = json.Unmarshal([]byte(optlyJson.payload), &optlyJson.data)
62+
}
63+
return optlyJson.data, err
64+
}
65+
66+
// GetValue populates the schema passed by the user - it takes primitive types and complex struct type
67+
func (optlyJson *OptimizelyJSON) GetValue(jsonPath string, schema interface{}) error {
68+
69+
populateSchema := func(v interface{}) error {
70+
jsonBytes, err := json.Marshal(v)
71+
if err != nil {
72+
return err
73+
}
74+
err = json.Unmarshal(jsonBytes, schema)
75+
return err
76+
}
77+
78+
if jsonPath == "" { // populate the whole schema
79+
return json.Unmarshal([]byte(optlyJson.payload), schema)
80+
}
81+
82+
if optlyJson.data == nil {
83+
err := json.Unmarshal([]byte(optlyJson.payload), &optlyJson.data)
84+
if err != nil {
85+
return err
86+
}
87+
}
88+
splitJSONPath := strings.Split(jsonPath, ".")
89+
lastIndex := len(splitJSONPath) - 1
90+
91+
internalMap := optlyJson.data
92+
93+
for i := 0; i < len(splitJSONPath); i++ {
94+
95+
if splitJSONPath[i] == "" {
96+
return errors.New("json key cannot be empty")
97+
}
98+
99+
if item, ok := internalMap[splitJSONPath[i]]; ok {
100+
switch v := item.(type) {
101+
102+
case map[string]interface{}:
103+
internalMap = v
104+
if i == lastIndex {
105+
return populateSchema(v)
106+
}
107+
108+
default:
109+
if i == lastIndex {
110+
return populateSchema(v)
111+
}
112+
}
113+
} else {
114+
return fmt.Errorf(`json key "%s" not found`, splitJSONPath[i])
115+
}
116+
}
117+
118+
return nil
119+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
package optimizelyjson
2+
3+
import (
4+
"github.com/stretchr/testify/suite"
5+
"testing"
6+
)
7+
8+
type OptimizelyJsonTestSuite struct {
9+
suite.Suite
10+
data map[string]interface{}
11+
dynamicList []interface{}
12+
optimizelyJson *OptimizelyJSON
13+
payload string
14+
}
15+
16+
func (suite *OptimizelyJsonTestSuite) SetupTest() {
17+
18+
suite.dynamicList = []interface{}{"1", "2", 3.01, 4.23, true}
19+
suite.payload = `{"field1":1,"field2":2.5,"field3":"three","field4":{"inner_field1":3,"inner_field2":["1","2",3.01,4.23,true]},"field5":true,"field6":null}`
20+
21+
suite.data = map[string]interface{}{
22+
"field1": 1.0,
23+
"field2": 2.5,
24+
"field3": "three",
25+
"field4": map[string]interface{}{"inner_field1": 3.0, "inner_field2": suite.dynamicList},
26+
"field5": true,
27+
"field6": nil,
28+
}
29+
suite.optimizelyJson = NewOptimizelyJSONfromString(suite.payload)
30+
}
31+
32+
func (suite *OptimizelyJsonTestSuite) TestConstructors() {
33+
34+
object1 := NewOptimizelyJSONfromString(suite.payload)
35+
object2 := NewOptimizelyJSONfromMap(suite.data)
36+
37+
object1ToString, err1Str := object1.ToString()
38+
object2ToString, err2Str := object2.ToString()
39+
suite.NoError(err1Str)
40+
suite.NoError(err2Str)
41+
suite.Equal(object1ToString, object2ToString)
42+
43+
object1ToMap, err1Map := object1.ToMap()
44+
object2ToMap, err2Map := object2.ToMap()
45+
suite.NoError(err1Map)
46+
suite.NoError(err2Map)
47+
suite.Equal(object1ToMap, object2ToMap)
48+
49+
}
50+
51+
func (suite *OptimizelyJsonTestSuite) TestToMap() {
52+
53+
returnValue, err := suite.optimizelyJson.ToMap()
54+
suite.NoError(err)
55+
suite.Equal(suite.data, returnValue)
56+
}
57+
58+
func (suite *OptimizelyJsonTestSuite) TestToString() {
59+
60+
returnValue, err := suite.optimizelyJson.ToString()
61+
suite.NoError(err)
62+
expected := `{"field1":1,"field2":2.5,"field3":"three","field4":{"inner_field1":3,"inner_field2":["1","2",3.01,4.23,true]},"field5":true,"field6":null}`
63+
suite.Equal(expected, returnValue)
64+
}
65+
66+
func (suite *OptimizelyJsonTestSuite) TestGetValueInvalidJsonKeyEmptySchema() {
67+
emptyStruct := struct{}{}
68+
err := suite.optimizelyJson.GetValue("some_key", &emptyStruct)
69+
suite.Error(err)
70+
suite.Equal(`json key "some_key" not found`, err.Error())
71+
suite.Equal(struct{}{}, emptyStruct)
72+
}
73+
74+
func (suite *OptimizelyJsonTestSuite) TestGetValueInvalidJsonMultipleKeyEmptySchema() {
75+
emptyStruct := struct{}{}
76+
err := suite.optimizelyJson.GetValue("field3.some_key", &emptyStruct)
77+
suite.Error(err)
78+
suite.Equal(`json key "some_key" not found`, err.Error())
79+
suite.Equal(struct{}{}, emptyStruct)
80+
}
81+
82+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyEmptySchema() {
83+
emptyStruct := struct{}{}
84+
err := suite.optimizelyJson.GetValue("field4", &emptyStruct)
85+
suite.NoError(err)
86+
suite.Equal(struct{}{}, emptyStruct)
87+
}
88+
89+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonMultipleKeyWrongSchema() {
90+
emptyStruct := struct{}{}
91+
err := suite.optimizelyJson.GetValue("field4.inner_field1", &emptyStruct)
92+
suite.Error(err) // cannot unmarshal number into a struct
93+
suite.Equal(struct{}{}, emptyStruct)
94+
}
95+
96+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonMultipleKeyValidSchema() {
97+
var intValue int
98+
err := suite.optimizelyJson.GetValue("field4.inner_field1", &intValue)
99+
suite.NoError(err)
100+
suite.Equal(3, intValue)
101+
}
102+
103+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonMultipleKeyValidGenericSchema() {
104+
var value interface{}
105+
err := suite.optimizelyJson.GetValue("field4.inner_field2", &value)
106+
suite.NoError(err)
107+
suite.Equal(suite.dynamicList, value)
108+
}
109+
110+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyIntValue() {
111+
var intValue int
112+
err := suite.optimizelyJson.GetValue("field1", &intValue)
113+
suite.NoError(err)
114+
suite.Equal(1, intValue)
115+
}
116+
117+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyDoubleValue() {
118+
var doubleValue float64
119+
err := suite.optimizelyJson.GetValue("field2", &doubleValue)
120+
suite.NoError(err)
121+
suite.Equal(2.5, doubleValue)
122+
}
123+
124+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyStringValue() {
125+
var stringValue string
126+
err := suite.optimizelyJson.GetValue("field3", &stringValue)
127+
suite.NoError(err)
128+
suite.Equal("three", stringValue)
129+
}
130+
131+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyBoolValue() {
132+
var boolValue bool
133+
err := suite.optimizelyJson.GetValue("field5", &boolValue)
134+
suite.NoError(err)
135+
suite.Equal(true, boolValue)
136+
}
137+
138+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyNullValue() {
139+
emptyStruct := struct{}{}
140+
err := suite.optimizelyJson.GetValue("field6", &emptyStruct)
141+
suite.NoError(err)
142+
suite.Equal(struct{}{}, emptyStruct)
143+
}
144+
145+
func (suite *OptimizelyJsonTestSuite) TestGetValueInValidJsonKey() {
146+
emptyStruct := struct{}{}
147+
err := suite.optimizelyJson.GetValue("field4.", &emptyStruct)
148+
suite.Error(err)
149+
suite.Equal(struct{}{}, emptyStruct)
150+
}
151+
152+
func (suite *OptimizelyJsonTestSuite) TestGetValueEmptyJsonKeyEmptySchema() {
153+
emptyStruct := struct{}{}
154+
err := suite.optimizelyJson.GetValue("", &emptyStruct)
155+
suite.NoError(err)
156+
suite.Equal(struct{}{}, emptyStruct)
157+
}
158+
159+
func (suite *OptimizelyJsonTestSuite) TestGetValueEmptyJsonMultipleKeyEmptySchema() {
160+
emptyStruct := struct{}{}
161+
err := suite.optimizelyJson.GetValue("field4..some_field", &emptyStruct)
162+
suite.Error(err)
163+
suite.Equal(struct{}{}, emptyStruct)
164+
}
165+
166+
func (suite *OptimizelyJsonTestSuite) TestGetValueEmptyJsonKeyWholeSchema() {
167+
168+
type field4Struct struct {
169+
InnerField1 int `json:"inner_field1"`
170+
InnerField2 []interface{} `json:"inner_field2"`
171+
}
172+
173+
type schema struct {
174+
Field1 int
175+
Field2 float64
176+
Field3 string
177+
Field4 field4Struct
178+
Field5 bool
179+
Field6 interface{}
180+
}
181+
sc := schema{}
182+
err := suite.optimizelyJson.GetValue("", &sc)
183+
suite.NoError(err)
184+
185+
expected := schema{
186+
Field1: 1,
187+
Field2: 2.5,
188+
Field3: "three",
189+
Field4: field4Struct{InnerField1: 3, InnerField2: suite.dynamicList},
190+
Field5: true,
191+
Field6: nil,
192+
}
193+
suite.Equal(expected, sc)
194+
}
195+
196+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyPartialSchema() {
197+
198+
type schema struct {
199+
InnerField1 int `json:"inner_field1"`
200+
InnerField2 []interface{} `json:"inner_field2"`
201+
}
202+
203+
sc := schema{}
204+
err := suite.optimizelyJson.GetValue("field4", &sc)
205+
suite.NoError(err)
206+
207+
expected := schema{
208+
InnerField1: 3,
209+
InnerField2: suite.dynamicList,
210+
}
211+
suite.Equal(expected, sc)
212+
213+
// check if it does not destroy original object
214+
err = suite.optimizelyJson.GetValue("field4", &sc)
215+
suite.NoError(err)
216+
suite.Equal(expected, sc)
217+
}
218+
219+
func (suite *OptimizelyJsonTestSuite) TestGetValueValidJsonKeyArraySchema() {
220+
221+
var array []interface{}
222+
223+
err := suite.optimizelyJson.GetValue("field4.inner_field2", &array)
224+
suite.NoError(err)
225+
226+
suite.Equal("1", array[0])
227+
suite.Equal("2", array[1])
228+
suite.Equal(3.01, array[2])
229+
suite.Equal(4.23, array[3])
230+
231+
}
232+
233+
func TestOptimizelyJsonTestSuite(t *testing.T) {
234+
suite.Run(t, new(OptimizelyJsonTestSuite))
235+
}

0 commit comments

Comments
 (0)