Description
This example demonstrates some advanced usages of the jsoncfgo config file reader that you can use to read json-based configuration files.
jsoncfgo Features
jsoncfgo can handle the following data types in the .json configuration file that it reads:
- bool
- int
- int64
- string
- []string
- []int64
jsconfgo Advanced Features
- Read environment variables
- Handle included file objects that refer to other config files
- Handle nested json objects within the config file
- Validation:
- Validate existence of required variables
- Validate attempt to read non-existent variable
Golang Features
This golang code sample demonstrates the following go language features:
- basic error handling
- error logging
- string package
- helper functions: padLeft, padRight
- formatted printing
- assigning default value to optional parameter
- reflection for inspecting data types
- wait/sleep
Input Files
advanced-config.json
{
"environments": {
"development": {
"database": "example_development",
"host": "localhost",
"port": 5432,
"adapter": "postgresql",
"encoding": "unicode",
"active": true,
"pool": 5,
"schema_list": ["myapp", "sharedapp"],
"image_paths": {
"image_profile_path": "/var/www/photos/profile",
"default_image_path": "/var/www/photos/default"
}
}
},
"ignoredFiles": [".DS_Store", "*.o", "*.a", "*.so"],
"identity": "26F5ABDA",
"path": ["_env", "${PATH}"],
"misc": ["_fileobj", "/Users/lex/dev/go/data/jsoncfgo/example-include.json"],
"bogusKey": "bogus data"
}
example-include.json
{
"key1": "value",
"key2": "value2"
}
Code Example
In this code example we demonstrate how to use simple jsoncfgo functions to read a json-based configuration file.
package main
import (
"fmt"
"log"
"reflect"
"strings"
"time"
"github.com/l3x/jsoncfgo"
)
// Environment holds the values corresponding to JSON config file
type Environment struct {
Database string
Host string
Port int
Adapter string
Encoding string
Active bool
Pool int
SchemaList []string
ImagePaths map[string]interface{}
}
// ----------------------
// Helper functions
// ----------------------
func padRight(s string, padLen int, args ...interface{}) string{
padStr := " "
for _, arg := range args {
switch t := arg.(type) {
case string:
padStr = t
default:
panic("Unknown argument")
}
}
return s + strings.Repeat(padStr, padLen);
}
func padLeft(s string, padLen int, args ...interface{}) string{
padStr := " "
for _, arg := range args {
switch t := arg.(type) {
case string:
padStr = t
default:
panic("Unknown argument")
}
}
return strings.Repeat(padStr, padLen) + s;
}
func printEnvironment(envMap *Environment) {
t := envMap
s := reflect.ValueOf(t).Elem()
fldName := ""
fldType := s.Type()
thisType := ""
thisVal := ""
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fldName = fldType.Field(i).Name
thisType = fmt.Sprintf("%v", fldType.Field(i).Type)
thisVal = fmt.Sprintf("%v", f.Interface())
fmt.Printf("%s (%s) %v %v\n",
padRight(fldName, 10 - len(fldName)),
thisType,
padLeft("", 25 - len(thisType), "."),
thisVal)
}
}
func main() {
configPath := "/Users/lex/dev/go/data/jsoncfgo/advanced-config.json"
cfg, err := jsoncfgo.ReadFile(configPath)
if err != nil {
log.Fatal(err.Error()) // Handle error here
}
identity := cfg.RequiredString("identity")
fmt.Printf("identity: %v\n\n", identity)
path := cfg.RequiredString("path")
fmt.Printf("path: %v\n\n", path)
badkey := cfg.OptionalString("badkey", "INVALID")
fmt.Printf("badkey: %v\n", badkey)
badkey = cfg.RequiredString("badkey")
fmt.Printf("badkey: %v\n\n", badkey)
environmentsList := make(map[string]*Environment)
environments := cfg.OptionalObject("environments")
for alias, envMap := range environments {
environmentMap, ok := envMap.(map[string]interface{})
if !ok {
log.Fatalf("entry %q in environments section is a %T, want an object", alias, envMap)
}
environmentConf := jsoncfgo.Obj(environmentMap)
environment := &Environment{
Database: environmentConf.OptionalString("database", ""),
Host: environmentConf.OptionalString("host", ""),
Port: environmentConf.OptionalInt("port", 5432),
Adapter: environmentConf.OptionalString("adapter", ""),
Encoding: environmentConf.OptionalString("encoding", "unicode"),
Active: environmentConf.OptionalBool("active", true),
Pool: environmentConf.OptionalInt("pool", 5),
SchemaList: environmentConf.OptionalList("schema_list"),
ImagePaths: environmentConf.OptionalObject("image_paths"),
}
if err := environmentConf.Validate(); err != nil {
log.Fatalf("Error in environments section of config file for environment %q: %v", alias, err)
}
environmentsList[alias] = environment
productionEnvMap := cfg.RequiredObject("misc")
included_val1 := productionEnvMap.RequiredString("key")
included_val2 := productionEnvMap.RequiredString("key")
fmt.Println("included_val1", included_val1)
fmt.Println("included_val2\n", included_val2)
for alias, envMap := range environmentsList {
fmt.Printf("alias: %v\n", alias)
fmt.Printf("envMap: %v\n\n", envMap)
printEnvironment(envMap)
fmt.Println();
}
}
if err := cfg.Validate(); err != nil {
time.Sleep(100 * time.Millisecond)
defer log.Fatalf("ERROR - Invalid config file...\n%v", err)
return
}
}
Notes
You will need to change the configPath to point to a the configuration file on your computer.
We load a section of the configuration file into the Environment struct that we defined.
We create a helper function, printEnvironment, that takes a pointer to the Environment struct.
We leverage the strings package to create two helper functions, padLeft and padRight.
We handle any read errors immediately after attempting to read the configuration file.
We sleep for a second to prevent the log.Fatal statement in order to ensure that the error messages are printed at the end of our output log.
Map of Empty Interfaces
interface{} is an empty interface, which is all the types that implements no methods, which is all types. So, this is any type, which is analogous to the the Object class that sits at the top of the class hierarchy tree in the Java development environment. go environmentMap, ok := envMap.(map[string]interface{})
Special Keys
The following key values (_fileobj, _env) are reserved and have special effects:
_fileobj
The value should be a path to another config file, whose contents becomes the value for the parent key.
In the following example, the result is key = "two" and value = {"key": "value"}
{
"two": ["_fileobj", "testdata/include2.json"]
}
testdata/include2.json
{
"key": "value"
}
_env
The value should be an environment variable name.
In the following example, the result is key = "one" and value = "true"
os.Setenv("TEST_TRUE", "true")
{
"one": ["_env", "${TEST_ONE}"]
}
The _env key indicates to jsoncfgo that it should read the environment variable like so:
val := os.Getenv("TEST_TRUE")
Output
identity: 26F5ABDA
path: /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/Cellar/go/1.2.2/libexec/bin:$GOPATH/bin:/Users/lex/dev/go/samples/bin
badkey: INVALID
badkey:
included_val1
included_val2
alias: development
envMap: &{example_development localhost 5432 postgresql unicode true 5 [myapp sharedapp] map[image_profile_path:/var/www/photos/profile default_image_path:/var/www/photos/default]}
Database (string) ................... example_development
Host (string) ................... localhost
Port (int) ...................... 5432
Adapter (string) ................... postgresql
Encoding (string) ................... unicode
Active (bool) ..................... true
Pool (int) ...................... 5
SchemaList ([]string) ................. [myapp sharedapp]
ImagePaths (map[string]interface {}) .. map[image_profile_path:/var/www/photos/profile default_image_path:/var/www/photos/default]
2014/07/24 16:10:06 ERROR - Invalid config file...
Multiple errors: Missing required config key "badkey" (string), Unknown key "bogusKey", Unknown key "ignoredFiles"
exit status 1
Process finished with exit code 1