Skip to content

Commit

Permalink
Added jsonnode package
Browse files Browse the repository at this point in the history
  • Loading branch information
retrodaredevil committed Mar 9, 2024
1 parent 79cbf05 commit e1183d4
Show file tree
Hide file tree
Showing 9 changed files with 464 additions and 4 deletions.
2 changes: 2 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ This section contains notes about dependencies.

## To-Do

* Make data returned in the same order as the JSON specified it was
* GoDS seems like a good solution, but there is a problem with its linked hash map deserializer: https://github.com/emirpasic/gods/issues/171
* Add ability to move parsing options up and down
* Add support for secure variable data defined in the data source configuration
* The variables defined here cannot be overridden for any request - this is for security
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ services:
args:
grafana_image: ${GRAFANA_IMAGE:-grafana-oss}
# https://grafana.com/docs/grafana/latest/whatsnew/
grafana_version: ${GRAFANA_VERSION:-10.2.3}
grafana_version: ${GRAFANA_VERSION:-10.3.3}
ports:
- 3000:3000/tcp
volumes:
Expand Down
51 changes: 51 additions & 0 deletions pkg/util/jsonnode/array.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package jsonnode

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
)

type Array []Node

func (_ *Array) sealed() {}

func (a *Array) String() string {
var stringArray []string
for _, node := range *a {
stringArray = append(stringArray, node.String())
}
return "Array [" + strings.Join(stringArray, ", ") + "]"
}

func (a *Array) decodeJSON(startToken json.Token, decoder *json.Decoder) error {
if startToken != json.Delim('[') {
return errors.New(fmt.Sprintf("Token is not the start of an array! Token: %v", startToken))
}
for { // inside the array
token, err := decoder.Token()
if err != nil {
return err
}
if token == json.Delim(']') {
return nil
}
node, err := decodeNode(token, decoder)
if err != nil {
return err
}
*a = append(*a, node)
}
}

func (a *Array) UnmarshalJSON(data []byte) error {
d := createDecoder(bytes.NewReader(data))

var token, err = d.Token()
if err != nil {
return err
}
return a.decodeJSON(token, d)
}
36 changes: 36 additions & 0 deletions pkg/util/jsonnode/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package jsonnode

import (
"encoding/json"
"errors"
"fmt"
"io"
)

func createDecoder(r io.Reader) *json.Decoder {
d := json.NewDecoder(r)
d.UseNumber()
return d
}

func decodeNode(token json.Token, decoder *json.Decoder) (Node, error) {
{ // test if it's an array
var nestedArray = Array{}
err := nestedArray.decodeJSON(token, decoder)
if err == nil {
return &nestedArray, nil
}
}
{ // test if it's an object
var nestedObject = Object{}
err := nestedObject.decodeJSON(token, decoder)
if err == nil {
return &nestedObject, nil
}
}
primitiveNode := parsePrimitive(token)
if primitiveNode != nil {
return primitiveNode, nil
}
return nil, errors.New(fmt.Sprintf("Unknown token: %v", token))
}
148 changes: 148 additions & 0 deletions pkg/util/jsonnode/json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package jsonnode

import (
"encoding/json"
"testing"
)

func TestPrimitivesAndNestedArray(t *testing.T) {
jsonString := `
[
true,
"asdf",
null,
1.1,
[1, 2]
]
`
var array = Array{}
//err := array.UnmarshalJSON([]byte(jsonString))
err := json.Unmarshal([]byte(jsonString), &array)
if err != nil {
t.Fatal("Could not unmarshal JSON", err)
return
}
if len(array) == 0 {
t.Error("Empty array")
return
}
if value, ok := array[0].(*Boolean); !ok || *value != true {
if !ok {
t.Error("First element is not a boolean")
return
}
t.Error("First element's value is not true!")
return
}
if value, ok := array[1].(*String); !ok || *value != "asdf" {
t.Error("Second element's value is not asdf!")
return
}
if value, ok := array[2].(Null); !ok || value != NULL {
t.Error("Third element's value is not null!")
return
}
if value, ok := array[3].(*Number); !ok || value.Number() != "1.1" {
t.Error("Forth element's value is not 1.1!")
return
}
{
nestedArray, ok := array[4].(*Array)
if !ok {
t.Error("Fifth element is not an array!")
return
}
if len(*nestedArray) != 2 {
t.Error("Incorrect data")
return
}
if value, ok := (*nestedArray)[0].(*Number); !ok || value.Number() != "1" {
t.Error("Incorrect data")
return
}
if value, ok := (*nestedArray)[1].(*Number); !ok || value.Number() != "2" {
t.Error("Incorrect data")
return
}
}
}
func TestObject(t *testing.T) {
jsonString := `
{
"a": 1,
"b": 2,
"c": 3
}
`
var object = NewObject()
err := json.Unmarshal([]byte(jsonString), &object)
if err != nil {
t.Fatal("Could not unmarshal JSON", err)
return
}
if len(object.Keys()) != 3 {
t.Error("Incorrect keys length")
return
}
{
nodeValue := object.Get("a")
if nodeValue == nil {
t.Error("No value for key a")
return
}
number, ok := nodeValue.(*Number)
if !ok {
t.Error("Value is not a number")
return
}
value, err := number.Int()
if err != nil {
t.Fatal(err)
return
}
if value != 1 {
t.Error("Value is incorrect")
}
}
{
nodeValue := object.Get("b")
if nodeValue == nil {
t.Error("No value for key b")
return
}
number, ok := nodeValue.(*Number)
if !ok {
t.Error("Value is not a number")
return
}
value, err := number.Int()
if err != nil {
t.Fatal(err)
return
}
if value != 2 {
t.Error("Value is incorrect")
}
}
{
nodeValue := object.Get("c")
if nodeValue == nil {
t.Error("No value for key c")
return
}
number, ok := nodeValue.(*Number)
if !ok {
t.Error("Value is not a number")
return
}
value, err := number.Int()
if err != nil {
t.Fatal(err)
return
}
if value != 3 {
t.Error("Value is incorrect")
}
}

}
9 changes: 9 additions & 0 deletions pkg/util/jsonnode/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package jsonnode

type Node interface {
sealed() // make this a sealed interface by having unexported methods

String() string
// json.Unmarshaler
UnmarshalJSON(data []byte) error
}
91 changes: 91 additions & 0 deletions pkg/util/jsonnode/object.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package jsonnode

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
)

type Object struct {
data map[string]Node
order []string
}

func NewObject() *Object {
object := Object{
data: map[string]Node{},
}
return &object
}

func (o *Object) decodeJSON(startToken json.Token, decoder *json.Decoder) error {
if startToken != json.Delim('{') {
return errors.New(fmt.Sprintf("Token is not the start of an object! Token: %v", startToken))
}
for {

keyToken, err := decoder.Token()
if err != nil {
return err
}
if keyToken == json.Delim('}') {
return nil
}
var key string
switch typedToken := keyToken.(type) {
case string:
key = typedToken
default:
return errors.New(fmt.Sprintf("invalid token for key to object. token: %v", keyToken))
}

token, err := decoder.Token()
if err != nil {
return err
}
valueNode, err := decodeNode(token, decoder)
if err != nil {
return err
}

_, keyExists := o.data[key]
if !keyExists {
o.order = append(o.order, key)
}
o.data[key] = valueNode
}
}

func (o *Object) UnmarshalJSON(data []byte) error {
d := createDecoder(bytes.NewReader(data))

var token, err = d.Token()
if err != nil {
return err
}
return o.decodeJSON(token, d)
}

func (_ *Object) sealed() {}

func (o *Object) String() string {
var result []string
for _, key := range o.Keys() {
value := o.Get(key)
result = append(result, fmt.Sprintf("%s : %s", key, value))
}
return strings.Join(result, ", ")
}

func (o *Object) Keys() []string {
return o.order
}
func (o *Object) Get(key string) Node {
node, exists := o.data[key]
if !exists {
return nil
}
return node
}
Loading

0 comments on commit e1183d4

Please sign in to comment.