Golang Code Examples

jsoncfgo - Advanced Usage

25 Jul 2014

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

References

comments powered by Disqus
The content of this site is licensed under the Creative Commons 3.0License and code is licensed under a BSD license