semita

package module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 13, 2021 License: MIT Imports: 9 Imported by: 8

README

consu/semita

Go Report Card PkgGoDev Actions Status codecov

Package semita provides utility functions to access data from a hierarchy structure.

Installation

$ go get -u github.com/btnguyen2k/consu/semita

Usage

A 'path' is used to specify the location of item in the hierarchy data. Sample of a path Employees.[1].first_name, where:

  • . (the dot character): path separator
  • Name: access attribute of a map/struct specified by 'Name'
  • [i]: access i'th element of a slice/array (0-based)
  • The dot right before [] can be omitted: Employees[1].first_name is equivalent to Employees.[1].first_name.

Notes:

  • Supported nested arrays, slices, maps and structs.
  • Struct's un-exported fields can be read, but not written.
  • Unaddressable structs and arrays are read-only.
  • The path separator can be change via PathSeparator variable (default value is .)

Example:

(more examples on project repository).

package main

import (
    "encoding/json"
    "fmt"
    "github.com/btnguyen2k/consu/reddo"
    "github.com/btnguyen2k/consu/semita"
)

func main() {
	fmt.Println("-========== Semina demo ==========-")
	data := map[string]interface{}{
		"name": map[string]interface{}{
			"first": "Thanh",
			"last":  "ngn",
		},
		"yob":   1981,
		"alias": []string{"btnguyen2k", "thanhnb"},
	}
	s := semita.NewSemita(data)
	var err error
	var v interface{}

	// current data tree: {"alias":["btnguyen2k","thanhnb"],"name":{"first":"Thanh","last":"ngn"},"yob":1981}
	tree := s.Unwrap()
	js, _ := json.Marshal(tree)
	fmt.Println("Data tree:", string(js))

	// get nested value
	v, err = s.GetValue("name.first") // v should be "Thanh"
	if err == nil {
		fmt.Println("Firstname:", v)
	} else {
		fmt.Println("Error:", err)
	}

	// set nested value
	err = s.SetValue("name.last", "Nguyen") // v should be "Nguyen" (instead of "ngn")
	if err == nil {
		v, err = s.GetValue("name.last")
		if err == nil {
			fmt.Println("Lastname:", v)
		} else {
			fmt.Println("Error:", err)
		}
	} else {
		fmt.Println("Error:", err)
	}

	// get a value and its type
	yob, err := s.GetValue("yob") // yob should be int(1981)
	if err == nil {
		fmt.Println("YOB:", yob.(int))
	} else {
		fmt.Println("Error:", err)
	}

	// get a value and type
	yob, err = s.GetValueOfType("yob", reddo.TypeUint) // yob should be uint64(1981)
	if err == nil {
		fmt.Println("YOB:", yob.(uint64)) // all uint types are returned as uint64
	} else {
		fmt.Println("Error:", err)
	}

	// append new item to end of slice
	err = s.SetValue("alias[]", "another")
	if err == nil {
		// either alias[2] or alias.[2] is accepted
		alias, err := s.GetValue("alias.[2]") // alias should be "another"
		if err == nil {
			fmt.Println("New Alias:", alias)
		} else {
			fmt.Println("Error:", err)
		}

		allAlias, err := s.GetValue("alias") // allAlias should be ["btnguyen2k","thanhnb","another"]
		if err == nil {
			fmt.Println("All Alias:", allAlias)
		} else {
			fmt.Println("Error:", err)
		}
	} else {
		fmt.Println("Error:", err)
	}

	// create missing nodes along the path
	err = s.SetValue("a.b[].c.d", true)
	if err == nil {
		// missing nodes should be created
		// data tree should be: {"a":{"b":[{"c":{"d":true}}]},"alias":["btnguyen2k","thanhnb","another"],"name":{"first":"Thanh","last":"Nguyen"},"yob":1981}
		tree := s.Unwrap()
		js, _ := json.Marshal(tree)
		fmt.Println("Data tree:", string(js))
	} else {
		fmt.Println("Error:", err)
	}
}

History

2021-01-13 - v0.1.5
  • PathSeparator is now configurable (default value is .).
2019-04-12 - v0.1.4.1
  • Upgrade to consu/reddo-v0.1.6:
    • Return zero value when input is nil.
2019-04-04 - v0.1.4
  • Migrate to Go modular design.
2019-03-07 - v0.1.2
  • Upgrade to consu/reddo-v0.1.3:
    • New functions GetTime(path string) (time.Time, error) and GetTimeWithLayout(path, layout string) (time.Time, error)
2019-03-05 - v0.1.1
  • Compatible with consu/reddo-v0.1.2
2019-02-22 - v0.1.0

First release:

  • Supported nested arrays, slices, maps and structs.
  • Struct's un-exported fields can be read, but not written.
  • Unaddressable structs and arrays are read-only.

Documentation

Overview

Package semita provides utility functions to access data from a hierarchy structure.

A "path" is used to specify the location of item in the hierarchy data. Sample of a path

"Employees.[1].first_name"

where:

  • "." (the dot character): path separator
  • "Name": access attribute of a map/struct specified by "Name"
  • "[i]": access i'th element of a slice/array (0-based)
  • The dot right before "[]" can be omitted: "Employees[1].first_name" is equivalent to "Employees.[1].first_name".

Notes:

  • Supported nested arrays, slices, maps and structs.
  • Struct's un-exported fields can be read, but not written.
  • Unaddressable structs and arrays are read-only.
  • The path separator can be change via semita.PathSeparator variable (default value is '.')

Example: (more examples at https://github.com/btnguyen2k/consu/tree/master/semita/examples)

package main

import (
	"encoding/json"
	"fmt"
	"github.com/btnguyen2k/consu/reddo"
	"github.com/btnguyen2k/consu/semita"
)

func main() {
	fmt.Println("-========== Semina demo ==========-")
	data := map[string]interface{}{
		"name": map[string]interface{}{
			"first": "Thanh",
			"last":  "ngn",
		},
		"yob":   1981,
		"alias": []string{"btnguyen2k", "thanhnb"},
	}
	s := semita.NewSemita(data)
	var err error
	var v interface{}

	// current data tree: {"alias":["btnguyen2k","thanhnb"],"name":{"first":"Thanh","last":"ngn"},"yob":1981}
	tree := s.Unwrap()
	js, _ := json.Marshal(tree)
	fmt.Println("Data tree:", string(js))

	// get nested value
	v, err = s.GetValue("name.first") // v should be "Thanh"
	if err == nil {
		fmt.Println("Firstname:", v)
	} else {
		fmt.Println("Error:", err)
	}

	// set nested value
	err = s.SetValue("name.last", "Nguyen") // v should be "Nguyen" (instead of "ngn")
	if err == nil {
		v, err = s.GetValue("name.last")
		if err == nil {
			fmt.Println("Lastname:", v)
		} else {
			fmt.Println("Error:", err)
		}
	} else {
		fmt.Println("Error:", err)
	}

	// get a value and its type
	yob, err := s.GetValue("yob") // yob should be int(1981)
	if err == nil {
		fmt.Println("YOB:", yob.(int))
	} else {
		fmt.Println("Error:", err)
	}

	// get a value and type
	yob, err = s.GetValueOfType("yob", reddo.TypeUint) // yob should be uint64(1981)
	if err == nil {
		fmt.Println("YOB:", yob.(uint64)) // all uint types are returned as uint64
	} else {
		fmt.Println("Error:", err)
	}

	// append new item to end of slice
	err = s.SetValue("alias[]", "another")
	if err == nil {
		// either alias[2] or alias.[2] is accepted
		alias, err := s.GetValue("alias.[2]") // alias should be "another"
		if err == nil {
			fmt.Println("New Alias:", alias)
		} else {
			fmt.Println("Error:", err)
		}

		allAlias, err := s.GetValue("alias") // allAlias should be ["btnguyen2k","thanhnb","another"]
		if err == nil {
			fmt.Println("All Alias:", allAlias)
		} else {
			fmt.Println("Error:", err)
		}
	} else {
		fmt.Println("Error:", err)
	}

	// create missing nodes along the path
	err = s.SetValue("a.b[].c.d", true)
	if err == nil {
		// missing nodes should be created
		// data tree should be: {"a":{"b":[{"c":{"d":true}}]},"alias":["btnguyen2k","thanhnb","another"],"name":{"first":"Thanh","last":"Nguyen"},"yob":1981}
		tree := s.Unwrap()
		js, _ := json.Marshal(tree)
		fmt.Println("Data tree:", string(js))
	} else {
		fmt.Println("Error:", err)
	}
}

Index

Constants

View Source
const (
	// Version defines version number of this package
	Version = "0.1.5"
)

Variables

View Source
var (
	// PathSeparator separates path components
	PathSeparator byte = '.'
)

Functions

func CreateZero

func CreateZero(t reflect.Type) reflect.Value

CreateZero create 'zero' value of specified type

  • if 't' is primitive type (bool, int, uint, float, complex, string, uintptr and unsafe-pointer): 'zero' value is created via reflect.Zero(t)
  • if 't' is array or slice: returns empty slice of type 't'
  • if 't' is map: returns empty map of type 't'
  • if 't' is struct: returns empty struct of type 't'
  • otherwise, return empty 'reflect.Value'

func GetTypeOfElement

func GetTypeOfElement(t interface{}) reflect.Type

GetTypeOfElement returns type of element of target 't'.

  • if 't' is an array, slice, map or channel (or pointer to an array, slice, map or channel): element type is returned
  • otherwise, t's type is return

func GetTypeOfMapKey

func GetTypeOfMapKey(m interface{}) reflect.Type

GetTypeOfMapKey returns type of map 'm”s key.

  • if 'm' is a map (or pointer to a map): type of map's key is returned
  • otherwise, nil is returned

func GetTypeOfStructAttibute

func GetTypeOfStructAttibute(s interface{}, attr string) reflect.Type

GetTypeOfStructAttibute returns type of a struct attribute.

  • if 's' is a struct (or pointer to a struct): type of attribute 'attr' is returned
  • if 's' is not a struct (or pointer to a struct) or attribute 'attr' does not exist: nil is returned

func SplitPath

func SplitPath(path string) []string

SplitPath splits a path into components.

Examples:

SplitPath("a.b.c.[i].d")     returns []string{"a", "b", "c", "[i]", "d"}
SplitPath("a.b.c[i].d")      returns []string{"a", "b", "c", "[i]", "d"}
SplitPath("a.b.c.[i].[j].d") returns []string{"a", "b", "c", "[i]", "[j]", "d"}
SplitPath("a.b.c[i].[j].d")  returns []string{"a", "b", "c", "[i]", "[j]", "d"}
SplitPath("a.b.c[i][j].d")   returns []string{"a", "b", "c", "[i]", "[j]", "d"}
SplitPath("a.b.c.[i][j].d")  returns []string{"a", "b", "c", "[i]", "[j]", "d"}

Types

type Semita

type Semita struct {
	// contains filtered or unexported fields
}

Semita struct wraps a underlying data store inside.

func NewSemita

func NewSemita(data interface{}) *Semita

NewSemita wraps the supplied 'data' inside a Semita instance and returns pointer to the Semita instance. data must be either array, slice, map or struct (or pointer fo them).

func (*Semita) GetTime

func (s *Semita) GetTime(path string) (time.Time, error)

GetTime returns a 'time.Time' located at 'path'.

Availability: This function is available since v0.1.2.

Notes:

  • Same rules/restrictions as of GetValue function.
  • If the value located at 'path' is 'time.Time': return it.
  • If the value is integer: depends on how big it is, treat the value as UNIX timestamp in seconds, milliseconds, microseconds or nanoseconds, convert to 'time.Time' and return the result.
  • If the value is string and convertible to integer: depends on how big it is, treat the value as UNIX timestamp in seconds, milliseconds, microseconds or nanoseconds, convert to 'time.Time' and return the result.
  • Otherwise, return error

Example:

data := map[string]interface{}{
	"ValueInt"    : 1547549353,
	"ValueString" : "1547549353123",
	"ValueInvalid": -1,
}
s := NewSetima(data)
s.GetTime("ValueInt")        returns Time(Tuesday, January 15, 2019 10:49:13.000 AM GMT), nil
s.GetTime("ValueString")     returns Time(Tuesday, January 15, 2019 10:49:13.123 AM GMT), nil
s.GetTime("ValueInvalid")    returns _, error

func (*Semita) GetTimeWithLayout

func (s *Semita) GetTimeWithLayout(path, layout string) (time.Time, error)

GetTimeWithLayout returns a 'time.Time' located at 'path'.

Availability: This function is available since v0.1.2.

Notes:

  • Same rules/restrictions as of GetTime function, plus:
  • If the value is string and NOT convertible to integer: 'layout' is used to convert the value to 'time.Time'. Error is returned if conversion fails.

Example:

data := map[string]interface{}{
	"ValueInt"    : 1547549353,
	"ValueString" : "1547549353123",
	"ValueInvalid": -1,
	"ValueDateStr": "January 15, 2019 20:49:13.123",
}
s := NewSetima(data)
s.GetTimeWithLayout("ValueInt", _)                                      returns Time(Tuesday, January 15, 2019 10:49:13.000 AM GMT), nil
s.GetTimeWithLayout("ValueString", _)                                   returns Time(Tuesday, January 15, 2019 10:49:13.123 AM GMT), nil
s.GetTimeWithLayout("ValueInvalid", _)                                  returns _, error
s.GetTimeWithLayout("ValueDateStr", "January 02, 2006 15:04:05.000")    returns Time(Tuesday, January 15, 2019 08:49:13.123 PM GMT), nil

func (*Semita) GetValue

func (s *Semita) GetValue(path string) (interface{}, error)

GetValue returns a value located at 'path'.

Notes:

  • Wrapped data must be either a struct, map, array or slice
  • map's keys must be strings
  • Nested structure is supported (e.g. array inside a map, inside a struct, inside a slice, etc)
  • Getting value of struct's unexported field is supported
  • If index is out-of-bound, (nil, nil) is returned

Example:

  data := map[string]interface{}{
	"Name": "Monster Corp.",
	"Year": 2003,
	"Employees": []map[string]interface{}{
	  {
		"first_name": "Mike",
		"last_name" : "Wazowski",
		"email"     : "[email protected]",
		"age"       : 29,
		"options"   : map[string]interface{}{
		  "work_hours": []int{9, 10, 11, 12, 13, 14, 15, 16},
		  "overtime"  : false,
		},
	  },
	  {
		"first_name": "Sulley",
		"last_name" : "Sullivan",
		"email"     : "[email protected]",
		"age"       : 30,
		"options"   : map[string]interface{}{
		  "work_hours": []int{13, 14, 15, 16, 17, 18, 19, 20},
		  "overtime"  :   true,
		},
	  },
	},
  }
  s := NewSetima(data)
  s.GetValue("Name")              // "Monster Corp."
  s.GetValue("Employees[0].age")  // 29

func (*Semita) GetValueOfType

func (s *Semita) GetValueOfType(path string, typ reflect.Type) (interface{}, error)

GetValueOfType retrieves value located at 'path', converts the value to target type and returns it.

Notes:

  • Wrapped data must be either a struct, map, array or slice
  • map's keys must be strings
  • Nested structure is supported (e.g. array inside a map, inside a struct, inside a slice, etc)
  • Getting value of struct's unexported field is supported
  • If index is out-of-bound, (nil, nil) is returned

Example:

data := map[string]interface{}{
	"Name": "Monster Corp.",
	"Year": 2003,
	"Employees": []map[string]interface{}{
		{
			"first_name": "Mike",
			"last_name" : "Wazowski",
			"email"     : "[email protected]",
			"age"       : 29,
			"options"   : map[string]interface{}{
		  	"work_hours": []int{9, 10, 11, 12, 13, 14, 15, 16},
		  	"overtime"  : false,
		},
		},
		{
			"first_name": "Sulley",
			"last_name" : "Sullivan",
			"email"     : "[email protected]",
			"age"       : 30,
			"options"   : map[string]interface{}{
				"work_hours": []int{13, 14, 15, 16, 17, 18, 19, 20},
				"overtime"  :   true,
			},
		},
	},
}
s := NewSetima(data)
var Name string = s.GetValueOfType("Name", reddo.TypeString).(string)            // "Monster Corp."
var age int64   = s.GetValueOfType("Employees[0].age", reddo.TypeInt).(int64)    // 29

func (*Semita) SetValue

func (s *Semita) SetValue(path string, value interface{}) error

SetValue sets a value to position specified by 'path'.

Notes:

  • Wrapped data must be either a struct, map, array or slice
  • map's keys must be strings
  • If 'path' points to a map's key, the key must be exported
  • Nested structure is supported (e.g. array inside a map, inside a struct, inside a slice, etc)
  • If child nodes along the path does not exist, this function will create them
  • If index is out-of-bound, this function returns error

Example:

data := map[string]interface{}{}
s := NewSetima(data)
s.SetValue("Name", "Monster Corp.") // data is now {"Name":"Monster Corp."}
s.SetValue("Year", 2013)            // data is now {"Name":"Monster Corp.", "Year":2013}
s.SetValue("employees[0].age", 29)  // data is now {"Name":"Monster Corp.", "Year":2013, "employees":[{"age":29}]}

func (*Semita) Unwrap

func (s *Semita) Unwrap() interface{}

Unwrap returns the underlying data

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL