Skip to content

Commit

Permalink
add support for JSONC in config
Browse files Browse the repository at this point in the history
  • Loading branch information
feliixx committed May 6, 2022
1 parent ea09f10 commit 79ba0b2
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A small configuration library for go application with a viper-like API, but with

It supports:

* reading a config in JSON format
* reading a config in JSON or JSONC ( JSON with comments)
* setting default


Expand Down
66 changes: 62 additions & 4 deletions parser.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package boa

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand All @@ -15,22 +16,79 @@ var (
)

// ParseConfig reads the config from an io.Reader.
func ParseConfig(jsonConfig io.Reader) error {
//
// The config may be in JSON or in JSONC ( json with comment)
// Allowed format for comments are
// * single line ( //... )
// * multiligne ( /*...*/ )
func ParseConfig(jsoncConfig io.Reader) error {

jsonc, err := io.ReadAll(jsoncConfig)
if err != nil {
return fmt.Errorf("fail to read from reader: %v", err)
}

cleanJson := removeComment(jsonc)

d := json.NewDecoder(jsonConfig)
d := json.NewDecoder(bytes.NewReader(cleanJson))
d.UseNumber()

var src map[string]any
err := d.Decode(&src)
err = d.Decode(&src)
if err != nil {
return err
return fmt.Errorf("fail to parse JSON: %v", err)
}

flatten("", src, config)

return nil
}

func removeComment(src []byte) []byte {

output := make([]byte, 0, len(src))

var prev byte
inString := false
inSingleLineComment := false
inMultiligneComment := false

for _, b := range src {

switch b {

case '"':
inString = !inString
case '/':
if !inString && prev == '/' {
output = output[0 : len(output)-1]
inSingleLineComment = true
}

if inMultiligneComment && prev == '*' {
inMultiligneComment = false
continue
}

case '*':
if !inString && prev == '/' {
output = output[0 : len(output)-1]
inMultiligneComment = true
}
case '\n':
inSingleLineComment = false
}

prev = b

if !inSingleLineComment && !inMultiligneComment {
output = append(output, b)
}
}

return output
}

func flatten(prefix string, src map[string]any, dst map[string]any) {

if len(prefix) > 0 {
Expand Down
59 changes: 59 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,65 @@ func TestSetDefaults(t *testing.T) {
}
}

func TestRemoveComment(t *testing.T) {

config := `
{
/* some
multiline
comment */
"smtp": {
// single line
"enabled": true, // with trailing space
"host": "http://127.0.0.1",// without trailing space
"port": 55,
"pwd": "fhd/*|,;,bdo*/"
} /**/
}
// `

loadConfig(t, config)

tests := []struct {
name string
want any
got any
}{
{
name: "smtp.enabled",
want: true,
got: boa.GetBool("smtp.enabled"),
},
{
name: "smtp.host",
want: "http://127.0.0.1",
got: boa.GetString("smtp.host"),
},
{
name: "smtp.port",
want: 55,
got: boa.GetInt("smtp.port"),
},
{
name: "smtp.pwd",
want: "fhd/*|,;,bdo*/",
got: boa.GetString("smtp.pwd"),
},
}

for _, test := range tests {

tt := test
t.Run(tt.name, func(t *testing.T) {
if tt.want != tt.got {
t.Errorf("expected '%v' but got '%v'", tt.want, tt.got)
}
})
}
}

func loadConfig(t *testing.T, config string) {
err := boa.ParseConfig(strings.NewReader(config))
if err != nil {
Expand Down

0 comments on commit 79ba0b2

Please sign in to comment.