Skip to content

Commit

Permalink
Merge pull request #1 from jaksonlin/develop
Browse files Browse the repository at this point in the history
test so far it aligns to the go's json unmarshaller
  • Loading branch information
jaksonlin authored Oct 2, 2023
2 parents b4dad8b + bc5d2e6 commit 0f10698
Show file tree
Hide file tree
Showing 19 changed files with 4,604 additions and 70 deletions.
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ A simple Go json parser that support defining variables in json file.

## Usage

as a json template processor, it can be used in the following ways:

```go

package main
Expand All @@ -32,3 +34,69 @@ func main() {

```

as a json unmarshaller, (tag support will be added in the future)

```go

type SomeStruct struct {
Field1 string
Field2 bool
Field3 int
Field4 interface{}
}

testExample := SomeStruct{
Field1: "hello",
Field2: true,
Field3: 100,
Field4: nil,
}

data, _ := json.Marshal(testExample)
var out SomeStruct
err := jsonextend.Unmarshal(bytes.NewReader(data), nil, &out)
if err != nil {
t.FailNow()
}

```

as a dyanmaic json unmarshaller, you can use variable as a route table to route the value to the right field.

```go
type SomeStruct struct {
Field1 string
Field2 int
Field3 interface{}
}
testExample := `
{
"Field1": "hello ${var1}",
"${var2}": ${var2Value},
"Field3":${var3}
}`

variables := map[string]interface{}{
"var1": "world!",
"var2": "Field2",
"var2Value": 100,
"var3": []int{1, 2, 3},
}

var out SomeStruct
err := jsonextend.Unmarshal(strings.NewReader(testExample), variables, &out)
if err != nil {
t.FailNow()
}
if out.Field1 != "hello world!" {
t.FailNow()
}
if out.Field2 != 100 {
t.FailNow()
}
for i, v := range out.Field3.([]int) {
if v != i+1 {
t.FailNow()
}
}
```
8 changes: 4 additions & 4 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,14 @@ func (i *jzoneAST) createValueNodeForKVPairs(owner *JsonKeyValuePairNode, t AST_

func (i *jzoneAST) finlizeKVPair() error {
kvElement, err := i.astTrace.Pop() // pop the kv, because it should be finalized to objet now.
if err == util.ErrorEodOfStack {
if err == util.ErrorEndOfStack {
return ErrorASTStackEmpty
}
if kvElement.GetNodeType() != AST_KVPAIR {
return ErrorASTUnexpectedElement
}
kvOwnerObj, err := i.astTrace.Peek()
if err == util.ErrorEodOfStack {
if err == util.ErrorEndOfStack {
return ErrorASTStackEmpty
}
if kvOwnerObj.GetNodeType() != AST_OBJECT {
Expand All @@ -156,7 +156,7 @@ func (i *jzoneAST) finlizeKVPair() error {
func (i *jzoneAST) EncloseLatestElements() error {

itemToFinalize, err := i.astTrace.Pop()
if err == util.ErrorEodOfStack {
if err == util.ErrorEndOfStack {
i.state = AST_STATE_FINISHED
return nil
}
Expand All @@ -183,7 +183,7 @@ func (i *jzoneAST) storeFinlizedItemToOwner(itemToFinalize JsonNode) error {
fallthrough
case AST_ARRAY:
ownerElement, err := i.astTrace.Peek()
if err == util.ErrorEodOfStack {
if err == util.ErrorEndOfStack {
i.state = AST_STATE_FINISHED
return nil // last element in the stack, no owner
}
Expand Down
105 changes: 81 additions & 24 deletions ast/ast_node.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package ast

import "github.com/jaksonlin/go-jsonextend/util"
import (
"encoding/base64"

"github.com/jaksonlin/go-jsonextend/util"
)

type AST_NODETYPE byte

Expand Down Expand Up @@ -30,8 +34,12 @@ func nodeFactory(t AST_NODETYPE, value interface{}) (JsonNode, error) {
Value: make([]*JsonKeyValuePairNode, 0),
}, nil
case AST_KVPAIR:
node, ok := value.(JsonStringValueNode)
if !ok {
return nil, ErrorASTKeyValuePairNotStringAsKey
}
return &JsonKeyValuePairNode{
Key: value.(JsonNode),
Key: node,
}, nil
case AST_STRING:
return &JsonStringNode{
Expand Down Expand Up @@ -82,7 +90,17 @@ type JsonVisitor interface {

type JsonNode interface {
GetNodeType() AST_NODETYPE
Visit(visitor JsonVisitor)
Visit(visitor JsonVisitor) error
}

type JsonCollectionNode interface {
JsonNode
Length() int
}

type JsonStringValueNode interface {
JsonNode
GetValue() string
}

type JsonStringNode struct {
Expand All @@ -95,8 +113,35 @@ func (node *JsonStringNode) GetNodeType() AST_NODETYPE {
return AST_STRING
}

func (node *JsonStringNode) Visit(visitor JsonVisitor) {
visitor.VisitStringNode(node)
func (node *JsonStringNode) Visit(visitor JsonVisitor) error {
return visitor.VisitStringNode(node)
}

func (node *JsonStringNode) GetValue() string {
if len(node.Value) == 2 {
return "" // empty string with 2 double quotation marks only
} else {
return string(node.Value[1 : len(node.Value)-1])
}
}

func (node *JsonStringNode) ToArrayNode() (*JsonArrayNode, error) {

data, err := base64.StdEncoding.DecodeString(node.GetValue())
if err != nil {
return nil, err
}
rs := &JsonArrayNode{
Value: make([]JsonNode, 0, len(data)),
}
for _, n := range data {
v := uint8(n)
rs.Value = append(rs.Value, &JsonNumberNode{
Value: float64(v),
})
}
return rs, nil

}

type JsonNumberNode struct {
Expand All @@ -109,8 +154,8 @@ func (node *JsonNumberNode) GetNodeType() AST_NODETYPE {
return AST_NUMBER
}

func (node *JsonNumberNode) Visit(visitor JsonVisitor) {
visitor.VisitNumberNode(node)
func (node *JsonNumberNode) Visit(visitor JsonVisitor) error {
return visitor.VisitNumberNode(node)
}

type JsonBooleanNode struct {
Expand All @@ -123,8 +168,8 @@ func (node *JsonBooleanNode) GetNodeType() AST_NODETYPE {
return AST_BOOLEAN
}

func (node *JsonBooleanNode) Visit(visitor JsonVisitor) {
visitor.VisitBooleanNode(node)
func (node *JsonBooleanNode) Visit(visitor JsonVisitor) error {
return visitor.VisitBooleanNode(node)
}

type JsonNullNode struct {
Expand All @@ -137,8 +182,8 @@ func (node *JsonNullNode) GetNodeType() AST_NODETYPE {
return AST_NULL
}

func (node *JsonNullNode) Visit(visitor JsonVisitor) {
visitor.VisitNullNode(node)
func (node *JsonNullNode) Visit(visitor JsonVisitor) error {
return visitor.VisitNullNode(node)
}

type JsonArrayNode struct {
Expand All @@ -151,16 +196,20 @@ func (node *JsonArrayNode) GetNodeType() AST_NODETYPE {
return AST_ARRAY
}

func (node *JsonArrayNode) Visit(visitor JsonVisitor) {
visitor.VisitArrayNode(node)
func (node *JsonArrayNode) Visit(visitor JsonVisitor) error {
return visitor.VisitArrayNode(node)
}

func (node *JsonArrayNode) Append(n JsonNode) {
node.Value = append(node.Value, n)
}

func (node *JsonArrayNode) Length() int {
return len(node.Value)
}

type JsonKeyValuePairNode struct {
Key JsonNode
Key JsonStringValueNode
Value JsonNode
}

Expand All @@ -170,8 +219,8 @@ func (node *JsonKeyValuePairNode) GetNodeType() AST_NODETYPE {
return AST_KVPAIR
}

func (node *JsonKeyValuePairNode) Visit(visitor JsonVisitor) {
visitor.VisitKeyValuePairNode(node)
func (node *JsonKeyValuePairNode) Visit(visitor JsonVisitor) error {
return visitor.VisitKeyValuePairNode(node)
}

func (node *JsonKeyValuePairNode) IsFilled() bool {
Expand All @@ -188,27 +237,31 @@ func (node *JsonObjectNode) GetNodeType() AST_NODETYPE {
return AST_OBJECT
}

func (node *JsonObjectNode) Visit(visitor JsonVisitor) {
visitor.VisitObjectNode(node)
func (node *JsonObjectNode) Visit(visitor JsonVisitor) error {
return visitor.VisitObjectNode(node)
}

func (node *JsonObjectNode) Append(kvNode *JsonKeyValuePairNode) {
node.Value = append(node.Value, kvNode)
}

func (node *JsonObjectNode) Length() int {
return len(node.Value)
}

type JsonExtendedVariableNode struct {
Value []byte
Variable string
}

var _ JsonNode = &JsonObjectNode{}
var _ JsonNode = &JsonExtendedVariableNode{}

func (node *JsonExtendedVariableNode) GetNodeType() AST_NODETYPE {
return AST_VARIABLE
}

func (node *JsonExtendedVariableNode) Visit(visitor JsonVisitor) {
visitor.VisitVariableNode(node)
func (node *JsonExtendedVariableNode) Visit(visitor JsonVisitor) error {
return visitor.VisitVariableNode(node)
}

func (node *JsonExtendedVariableNode) extractVariable() {
Expand All @@ -221,14 +274,14 @@ type JsonExtendedStringWIthVariableNode struct {
Variables map[string][]byte
}

var _ JsonNode = &JsonObjectNode{}
var _ JsonNode = &JsonExtendedStringWIthVariableNode{}

func (node *JsonExtendedStringWIthVariableNode) GetNodeType() AST_NODETYPE {
return AST_STRING_VARIABLE
}

func (node *JsonExtendedStringWIthVariableNode) Visit(visitor JsonVisitor) {
visitor.VisitStringWithVariableNode(node)
func (node *JsonExtendedStringWIthVariableNode) Visit(visitor JsonVisitor) error {
return visitor.VisitStringWithVariableNode(node)
}

func (node *JsonExtendedStringWIthVariableNode) extractVariables() {
Expand All @@ -240,3 +293,7 @@ func (node *JsonExtendedStringWIthVariableNode) extractVariables() {
node.Variables[string(item[1])] = item[0]
}
}

func (node *JsonExtendedStringWIthVariableNode) GetValue() string {
return node.JsonStringNode.GetValue()
}
1 change: 1 addition & 0 deletions ast/err.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var (
ErrorASTEncloseElementType = errors.New("enclose element type must be array or object")
ErrorASTIncorrectNodeType = errors.New("incorrect node type")
ErrorIncorrectSyntaxSymbolForConstructAST = errors.New("incorrect character for construct ast")
ErrorASTKeyValuePairNotStringAsKey = errors.New("object key should be string")
)

var (
Expand Down
14 changes: 9 additions & 5 deletions ast/syntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (s *syntaxChecker) Length() int {
func (s *syntaxChecker) Enclose(b byte) error {

t, err := s.syntaxState.Pop()
if err == util.ErrorEodOfStack {
if err == util.ErrorEndOfStack {
return ErrorSyntaxEmptyStack
}
if t > AST_NODE_TYPE_BOUNDARY {
Expand All @@ -54,13 +54,14 @@ func (s *syntaxChecker) Enclose(b byte) error {
func (s *syntaxChecker) jsonArrayFormatCheck() error {
expectingValue := true
lastIsValue := false
hasEncounterValue := false
for {
t, err := s.syntaxState.Pop()
if err == util.ErrorEodOfStack {
if err == util.ErrorEndOfStack {
return ErrorSyntaxEmptyStack
}
if t == '[' {
if !lastIsValue {
if hasEncounterValue && !lastIsValue { // deal with [] | [,], the previous is ok hasNeverEncounterValue by pass to ok, later raise error
return ErrorSyntaxCommaBehindLastItem
}
// mark that here is an array in the syntax checker
Expand All @@ -72,6 +73,7 @@ func (s *syntaxChecker) jsonArrayFormatCheck() error {
return ErrorSyntaxElementNotSeparatedByComma
} else {
lastIsValue = true
hasEncounterValue = true
}
} else if !expectingValue {
if t > AST_NODE_TYPE_BOUNDARY {
Expand All @@ -90,15 +92,16 @@ func (s *syntaxChecker) jsonArrayFormatCheck() error {
func (s *syntaxChecker) jsonObjectCheck() error {
expectingValue := true // already pop the } | ]
lastIsValue := false
hasEncounterValue := false
expectingSymbol := byte(':') // first symbol to expect is : then , then : then , ...
for {
t, err := s.syntaxState.Pop()
if err == util.ErrorEodOfStack {
if err == util.ErrorEndOfStack {
return ErrorSyntaxEmptyStack
}
// check first otherwise drop into compare with allowed symbol
if t == '{' {
if !lastIsValue {
if hasEncounterValue && !lastIsValue {
return ErrorSyntaxCommaBehindLastItem
}
// enclose the object as a value in the syntax checker, this will save our hands in handling }} or ]} in the syntax checker
Expand All @@ -118,6 +121,7 @@ func (s *syntaxChecker) jsonObjectCheck() error {
return ErrorSyntaxExtendedSyntaxVariableAsKey
}
lastIsValue = true
hasEncounterValue = true
}
} else if !expectingValue {
if t > AST_NODE_TYPE_BOUNDARY {
Expand Down
1 change: 1 addition & 0 deletions ast/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package ast
Loading

0 comments on commit 0f10698

Please sign in to comment.