# Packages
# README
gtly - Dynamic data structure with go lang.
This library is compatible with Go 1.15+
Please refer to CHANGELOG.md
if you encounter breaking changes.
Motivation
The goal of this project is to use dynamic data types without defining native GO structs with minimum memory footprint. Alternative would be just using map, but that is way too inefficient. Having dynamic type safe objects enables building generic solution for REST/Micro Service/ETL etc. To build dynamic solution dynamic object should be easily transferable into common format like JSON, AVRO, ProtoBuf, etc....
Introduction
Gtly complex data type use runtime struct based storage with Proto reference to reduce memory footprint and to avoid reflection. An proto instance is shared across all Object and Collection of the same type. Proto control mapping between field and field position withing object, or slice item. What's more proto field define field meta data like DateLayout, OutputName controlled by proto CaseFormat dynamically.
Usage
Object
package mypacakge
import (
"fmt"
"github.com/viant/gtly"
"github.com/viant/toolbox/format"
"github.com/viant/gtly/codec/json"
"log"
"time"
)
func NewObject_Usage() {
fooProvider, err := gtly.NewProvider("foo",
gtly.NewField("id", gtly.FieldTypeInt),
gtly.NewField("firsName", gtly.FieldTypeString),
gtly.NewField("description", gtly.FieldTypeString, gtly.OmitEmptyOpt(true)),
gtly.NewField("updated", gtly.FieldTypeTime, gtly.DateLayoutOpt("2006-01-02T15:04:05Z07:00")),
gtly.NewField("numbers", gtly.FieldTypeArray, gtly.ComponentTypeOpt(gtly.FieldTypeInt)),
)
if err != nil {
log.Fatal(err)
}
foo1 := fooProvider.NewObject()
foo1.SetValue("id", 1)
foo1.SetValue("firsName", "Adam")
foo1.SetValue("updated", time.Now())
foo1.SetValue("numbers", []int{1, 2, 3})
JSON, err := json.Marshal(foo1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", JSON)
fooProvider.OutputCaseFormat(format.CaseLower, format.CaseUpperUnderscore)
JSON, _ = json.Marshal(foo1)
fmt.Printf("%s\n", JSON)
fooProvider.OutputCaseFormat(format.CaseLower, format.CaseLowerUnderscore)
foo1.SetValue("active", true)
foo1.SetValue("description", "some description")
JSON, _ = json.Marshal(foo1)
fmt.Printf("%s\n", JSON)
}
Array
package mypacakge
import (
"fmt"
"github.com/viant/gtly"
"github.com/viant/gtly/codec/json"
"log"
"time"
)
func NewArray_Usage() {
fooProvider, err := gtly.NewProvider("foo",
gtly.NewField("id", gtly.FieldTypeInt),
gtly.NewField("firsName", gtly.FieldTypeString),
gtly.NewField("income", gtly.FieldTypeFloat64),
gtly.NewField("description", gtly.FieldTypeString, gtly.OmitEmptyOpt(true)),
gtly.NewField("updated", gtly.FieldTypeTime, gtly.DateLayoutOpt(time.RFC3339)),
gtly.NewField("numbers", gtly.FieldTypeArray, gtly.ComponentTypeOpt(gtly.FieldTypeInt)),
)
if err != nil {
log.Fatal(err)
}
fooArray1 := fooProvider.NewArray()
setId := fooProvider.Mutator("id")
setIncome := fooProvider.Mutator("income")
setFirstName := fooProvider.Mutator("firsName")
for i := 0; i < 10; i++ {
foo1 := fooProvider.NewObject()
setId.Int(foo1, 1)
setIncome.Float64(foo1, 64000.0*float64(1+(10/(i+1))))
setFirstName.String(foo1, "Adam")
fooArray1.AddObject(foo1)
}
now := time.Now()
fooArray1.Add(map[string]interface{}{
"id": 100,
"firsName": "Tom",
"updated": now,
})
totalIncome := 0.0
incomeField := fooProvider.Proto.Accessor("income")
//Iterating collection
err = fooArray1.Objects(func(object *gtly.Object) (bool, error) {
fmt.Printf("id: %v\n", object.Value("id"))
fmt.Printf("name: %v\n", object.Value("name"))
value := incomeField.Float64(object)
totalIncome += value
return true, nil
})
fmt.Printf("income total: %v\n", totalIncome)
if err != nil {
log.Fatal(err)
}
JSON, err := json.Marshal(fooArray1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", JSON)
}
Map
package mypacakge
import (
"fmt"
"github.com/viant/gtly"
"github.com/viant/gtly/codec/json"
"log"
"time"
)
func NewMap_Usage() {
fooProvider, err := gtly.NewProvider("foo",
gtly.NewField("id", gtly.FieldTypeInt),
gtly.NewField("firsName", gtly.FieldTypeString),
gtly.NewField("description", gtly.FieldTypeString, gtly.OmitEmptyOpt(true)),
gtly.NewField("updated", gtly.FieldTypeTime, gtly.DateLayoutOpt(time.RFC3339)),
gtly.NewField("numbers", gtly.FieldTypeArray, gtly.ComponentTypeOpt(gtly.FieldTypeInt)),
)
if err != nil {
log.Fatal(err)
}
//Creates a map keyed by id Field
aMap := fooProvider.NewMap(gtly.NewKeyProvider("id"))
for i := 0; i < 10; i++ {
foo := fooProvider.NewObject()
foo.SetValue("id", i)
foo.SetValue("firsName", fmt.Sprintf("Name %v", i))
aMap.AddObject(foo)
}
//Accessing map
foo1 := aMap.Object("1")
JSON, err := json.Marshal(foo1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", JSON)
//Iterating map
err = aMap.Pairs(func(key interface{}, item *gtly.Object) (bool, error) {
fmt.Printf("id: %v\n", item.Value("id"))
fmt.Printf("name: %v\n", item.Value("name"))
return true, nil
})
if err != nil {
log.Fatal(err)
}
JSON, err = json.Marshal(aMap)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", JSON)
//[{"id":1,"firsName":"Name 1","updated":null,"numbers":null},{"id":3,"firsName":"Name 3","updated":null,"numbers":null},{"id":4,"firsName":"Name 4","updated":null,"numbers":null},{"id":6,"firsName":"Name 6","updated":null,"numbers":null},{"id":8,"firsName":"Name 8","updated":null,"numbers":null},{"id":9,"firsName":"Name 9","updated":null,"numbers":null},{"id":0,"firsName":"Name 0","updated":null,"numbers":null},{"id":2,"firsName":"Name 2","updated":null,"numbers":null},{"id":5,"firsName":"Name 5","updated":null,"numbers":null},{"id":7,"firsName":"Name 7","updated":null,"numbers":null}]
}
MultiMap
func NewMultiMap_Usage() {
fooProvider, err := gtly.NewProvider("foo",
gtly.NewField("id", gtly.FieldTypeInt),
gtly.NewField("firsName", gtly.FieldTypeString),
gtly.NewField("city", gtly.FieldTypeString, gtly.OmitEmptyOpt(true)),
gtly.NewField("updated", gtly.FieldTypeTime, gtly.DateLayoutOpt(time.RFC3339)),
gtly.NewField("numbers", gtly.FieldTypeArray, gtly.ComponentTypeOpt(gtly.FieldTypeInt)),
)
if err != nil {
log.Fatal(err)
}
//Creates a multi map keyed by id Field
aMap := fooProvider.NewMultimap(gtly.NewKeyProvider("city"))
for i := 0; i < 10; i++ {
foo := fooProvider.NewObject()
foo.SetValue("id", i)
foo.SetValue("firsName", fmt.Sprintf("Name %v", i))
if i%2 == 0 {
foo.SetValue("city", "Cracow")
} else {
foo.SetValue("city", "Warsaw")
}
aMap.AddObject(foo)
}
//Accessing map
fooInWarsawSlice := aMap.Slice("Warsaw")
JSON, err := json.Marshal(fooInWarsawSlice)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", JSON)
//Prints [{"id":1,"firsName":"Name 1","city":"Warsaw","updated":null,"numbers":null},{"id":3,"firsName":"Name 3","city":"Warsaw","updated":null,"numbers":null},{"id":5,"firsName":"Name 5","city":"Warsaw","updated":null,"numbers":null},{"id":7,"firsName":"Name 7","city":"Warsaw","updated":null,"numbers":null},{"id":9,"firsName":"Name 9","city":"Warsaw","updated":null,"numbers":null}]
//Iterating multi map
err = aMap.Slices(func(key interface{}, value *gtly.Array) (bool, error) {
fmt.Printf("%v -> %v\n", key, value.Size())
return true, nil
})
if err != nil {
log.Fatal(err)
}
JSON, err = json.Marshal(aMap)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", JSON)
//[{"id":0,"firsName":"Name 0","city":"Cracow","updated":null,"numbers":null},{"id":2,"firsName":"Name 2","city":"Cracow","updated":null,"numbers":null},{"id":4,"firsName":"Name 4","city":"Cracow","updated":null,"numbers":null},{"id":6,"firsName":"Name 6","city":"Cracow","updated":null,"numbers":null},{"id":8,"firsName":"Name 8","city":"Cracow","updated":null,"numbers":null},{"id":1,"firsName":"Name 1","city":"Warsaw","updated":null,"numbers":null},{"id":3,"firsName":"Name 3","city":"Warsaw","updated":null,"numbers":null},{"id":5,"firsName":"Name 5","city":"Warsaw","updated":null,"numbers":null},{"id":7,"firsName":"Name 7","city":"Warsaw","updated":null,"numbers":null},{"id":9,"firsName":"Name 9","city":"Warsaw","updated":null,"numbers":null}]
}
Contributing to gtly
Gtly is an open source project and contributors are welcome!
See TODO list
License
The source code is made available under the terms of the Apache License, Version 2, as stated in the file LICENSE
.
Individual files may be made available under their own specific license, all compatible with Apache License, Version 2. Please see individual files for details.
Credits and Acknowledgements
Library Author: Adrian Witas