yamc

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2023 License: MIT Imports: 10 Imported by: 10

README

Yet Another Map Converter

Description

this library is build to avoid the need of knowing the data structure for reading data files in different formats. so the problem is depending on two types of data structures you need to know to read data from a source and use them in your code:

  • Array: which is a list of values of the same type.
  • Object: which is a list of key-value pairs.
The Issue

for parsing unknown source data, you run in trouble, if you don't know the "root" data structure.

objects read as map[string]interface{}

{
    "name": "John Doe",
    "age": 30,
    "cars": [
        "Ford",
        "BMW",
        "Fiat"
    ]
}

and arrays read as []interface{}

[
    "Ford",
    "BMW",
    "Fiat"
]

so you need to know the data structure to read the data, and use it in your code. or you will get an error. like this one:

jsonSource := []byte(`[
		{
			"id": 1,
			"name": "John Doe",
			"email": "sfjhg@sdfkfg.net",
			"phone": "555-555-5555"
		},
		{
			"id": 2,
			"name": "Jane Doe",
			"email": "lllll@jjjjj.net",
			"phone": "555-555-5521"
		},
		{
			"id": 3,
			"name": "John Smith",
			"email": "kjsahd@fjjgg.net",
			"phone": "888-555-5555"
		}
	]`)

	var data map[string]interface{}
	if err := json.Unmarshal(jsonSource, &data); err != nil {
		panic(err)
	}

this would result in the following error:

panic: json: cannot unmarshal array into Go value of type map[string]interface {}

the code is obvious, we are trying to unmarshal an array into a map, which is not possible. all of them is fine if we can trust the source data, but what if we can't? for example to have some more generic code, or to read data from a user input, or a file, or a database, or a network connection, or ... .

The Solution

YAMC is an abbreviation for Yet Another Map Converter, which is a library to convert data from one data structure to another. it is a simple library, which is build to solve the problem of reading data from unknown sources, and convert them to a known data structure, so you can use them in your code.

the goal is to have a library that reads data from different sources, depending on Readers specialized to a Datatype (json yaml and so on) , and convert them to a known data structure, and provide a simple API to get the data from the source.

one usecase is to have no longer taking care about value files they are used together with go/template, and you even not know what value files provide what data, and how they paths are composed. because booth is then depending the specific usage later on.

depending this data:

for the example we use just an byte array.

jsonSource := []byte(`[
   {
        "id": 1,
        "name": "John Doe",
        "email": ""
    },
]`)
Create an instance of Yamc
mapData := yamc.New()
use an Reader to parse the data

if err := mapData.Parse(yamc.NewJsonReader(), jsonSource); err != nil {
    panic(err)
}
get what structure is used

this is useful if you want to know what data structure was used to parse the data. at least if you have to compose paths to get the data from the source.

switch mapData.GetSourceDataType() {
case yamc.TYPE_ARRAY:
	fmt.Println("source data was in form of []interface{}")
case yamc.TYPE_STRING_MAP:
	fmt.Println("source data was in form of map[string]interface{}")
default:
	fmt.Println("this should not happen")
}
get the data from the source

a simple dotted path where each string is used as keyname, and each number is used as index. 0.name would be the name of the first element in the array. index 0 the element named "name".

data, err := mapData.Get("0.name")
if err != nil {
    panic(err)
}
fmt.Println(fmt.Sprintf("%v", data))
more complex example paths

for more complex paths, YAMC implements tidwall/gson so you can use the same syntax as in gson.

source := []byte(`{
	"name": {"first": "Tom", "last": "Anderson"},
	"age":37,
	"children": ["Sara","Alex","Jack"],
	"fav.movie": "Deer Hunter",
	"friends": [
	  {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
	  {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
	  {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
	]
  }`)

data := yamc.New()
data.Parse(yamc.NewJsonReader(), source)

paths := []string{
	"name.last",
	"age",
	"children",
	"children.#",
	"children.1",
	"child*.2",
	"c?ildren.0",
	"fav\\.movie",
	"friends.#.first",
	"friends.1.last",
	"friends.#(last==\"Murphy\").first",
	"friends.#(last==\"Murphy\")#.first",
	"friends.#(age>45)#.last",
	"friends.#(first%\"D*\").last",
	"friends.#(first!%\"D*\").last",
	"friends.#(nets.#(==\"fb\"))#.first",
}
// just use any of the examples from gson README.md (https://github.com/tidwall/gjson)
for _, gsonPath := range paths {
	name, _ := data.GetGjsonString(gsonPath)	
	fmt.Println(gsonPath, " => ", name)
}

see in example/gson/main.go.

conversion

YAMC depending of the reader interface. there a 2 implementations for json and yaml. so you can read data from one source and convert it to another format.

data := []byte(`{"age": 45, "hobbies": ["golf", "reading", "swimming"]}`)
conv := yamc.New()
if err := conv.Parse(yamc.NewJsonReader(), data); err != nil {
	panic(err)
} else {
	if str, err2 := conv.ToString(yamc.NewYamlReader()); err2 != nil {
		panic(err2)
	} else {
		fmt.Println(str)
	}
}

will result in:

age: 45
hobbies:
    - golf
    - reading
    - swimming

Documentation

Overview

Copyright (c) 2022 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.

Licensed under the MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Copyright (c) 2022 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.

Licensed under the MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Copyright (c) 2022 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.

Licensed under the MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Copyright (c) 2022 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.

Licensed under the MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Copyright (c) 2022 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.

Licensed under the MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Index

Constants

View Source
const (
	TYPE_ARRAY      = 1 // source type []interface{}
	TYPE_STRING_MAP = 2 // ... map[string]interface{}
	UNSET           = 0 // initial status
)

Variables

This section is empty.

Functions

func CanHandleValue

func CanHandleValue(value any) bool

CanHandleValue just make sure we do not run in panic because of an dataype we nor able to handle

func FindChain

func FindChain(in interface{}, byChain ...string) (data interface{}, err error)

FindChain is trying to get a value from different types of slices and maps. this are limited to the most used data structures, they needed to work with json and yaml content

func IsPointer

func IsPointer(i interface{}) bool

IsPointer checks if the given interface is a pointer

Types

type DataReader

type DataReader interface {
	Unmarshal(in []byte, out interface{}) (err error)                // Unmarshal is used to decode the data
	Marshal(in interface{}) (out []byte, err error)                  // Marshal is used to encode the data
	FileDecode(path string, decodeInterface interface{}) (err error) // FileDecode is used to decode the data from a file
	SupportsExt() []string                                           // SupportsExt returns a list of supported file extensions
	HaveFields() bool                                                // HaveFields returns true if the data reader have fields they can report
	GetFields() *StructDef                                           // GetFields returns the fields of the data reader
}

DataReader is the interface for the data reader the data reader have to support the regular unmarshal and marshal. additionally it have to support the file decode. the filedecode is as shortcut for read the file and decode it. this part of the interface is used to get the data from the file depending on the file extension. for this, the SupportsExt returns a list of supported file extensions.

the HaveFields is used to tell the caller if the data reader have fields they can report. this can be used to work with data tags for validateing data. depending on the type of the source structure, it is not guaranteed that the data reader have fields they can report. if we have fields to report, read them with GetFields.

type JsonReader

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

func NewJsonReader

func NewJsonReader() *JsonReader

func (*JsonReader) FileDecode

func (j *JsonReader) FileDecode(path string, decodeInterface interface{}) (err error)

func (*JsonReader) GetFields

func (j *JsonReader) GetFields() *StructDef

func (*JsonReader) HaveFields

func (j *JsonReader) HaveFields() bool

func (*JsonReader) Marshal

func (j *JsonReader) Marshal(in interface{}) (out []byte, err error)

func (*JsonReader) SupportsExt

func (j *JsonReader) SupportsExt() []string

func (*JsonReader) Unmarshal

func (j *JsonReader) Unmarshal(in []byte, out interface{}) (err error)

type ReflectTagRef

type ReflectTagRef struct {
	// if the field is renamed by a tag, we put this name here
	TagRenamed string

	// if we have additional tags, we put them here
	TagAdditional []string
}

type StructDef

type StructDef struct {
	// if true, the struct is initialized
	Init bool

	// the struct we want to read
	Struct interface{}

	// the fields of the struct
	Fields map[string]StructField

	// if the struct is ignored, we store the reason here
	IgnoredBecauseOf string
	// contains filtered or unexported fields
}

func NewStructDef

func NewStructDef(strct interface{}) *StructDef

NewStructDef returns a new struct reader

func (*StructDef) AddToIndex

func (s *StructDef) AddToIndex(field ...string)

func (*StructDef) DetectIndentCount

func (s *StructDef) DetectIndentCount(withField string) int

check the index entries and calculate the indent level we need to find the indent level to be able to read the struct

func (*StructDef) GetField

func (s *StructDef) GetField(field string) (StructField, error)

GetField returns the field information for the given field name if the field name contains a dot, we try to find the child

func (*StructDef) GetFieldByTag

func (s *StructDef) GetFieldByTag(tag string) (StructField, error)

func (*StructDef) GetOrderedIndexSlice

func (s *StructDef) GetOrderedIndexSlice() []string

func (*StructDef) ReadStruct

func (s *StructDef) ReadStruct(tagparser reftagFunc) error

ReadStruct reads the given struct and returns a map with all values the tagparser is a function that can be used to parse the tag by an specialized data reader, so the reader can solve the tag. the tagparser can be nil, if you don't need it.

func (*StructDef) ResetIndexSlice

func (s *StructDef) ResetIndexSlice()

func (*StructDef) SetAllowedTagSearch

func (s *StructDef) SetAllowedTagSearch(allow bool)

SetAllowedTagSearch allows to search for fields by tag. example: Username string `json:"username"` will be found by "username" and "Username". This is disabled by default, because it is slower but the only way to find fields if we have the tags only.

func (*StructDef) SetIndexSlice

func (s *StructDef) SetIndexSlice(slice []string)

func (*StructDef) SetMaxIdentDiff

func (s *StructDef) SetMaxIdentDiff(diff int) *StructDef

SetMaxIdentDiff sets the max ident diff. the default is 1. if you have a json struct, you might to set this to 2 depending if you have a json struct like this:

Targets: {
 Labels: [
  "payed",
  "not payed",
  "important",
  "not important",
 ],
 Worker: [
  {
   Name: "hello",
   SureName: "world",
  },

because of the json format, the ident level is once added for objects in lists. depends on the list tag [ followed by the object tag {. note: depending on formating of course.

type StructField

type StructField struct {
	// the name of the field
	Name string
	// the path of the field in relation to the struct
	Path string
	// the type of the field
	Type string
	// the tag of the field
	Tag reflect.StructTag
	// reader depending...
	OrginalTag ReflectTagRef
	// if we are a node, we have children
	Children map[string]StructField
	// for faster access store the amount of children
	ChildLen int
}

type Yamc

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

func New

func New() *Yamc

New returns a new Yamc instance

func NewByFile

func NewByFile(filename string, rdr DataReader) (*Yamc, error)

NewByFile loads file content and returns a new Ymac

func NewByJson

func NewByJson(filename string) (*Yamc, error)

NewByJson json file loading shortcut

func NewByYaml

func NewByYaml(filename string) (*Yamc, error)

NewByYaml shortcut for reading Yaml File by using NewYmacByFile

func (*Yamc) Delete

func (y *Yamc) Delete(key string)

Delete is just a wrapper for the sync.Map Delete function

func (*Yamc) FindValue

func (y *Yamc) FindValue(path string) (content any, err error)

GetGjsonString returns the content of the path as json string result or the error while processing the data

func (*Yamc) Get

func (y *Yamc) Get(key string) (interface{}, bool)

Get is just a wrapper for the sync.Map Load function

func (*Yamc) GetData

func (y *Yamc) GetData() map[string]interface{}

GetData is just the getter for the actual data. this is independend if they are loaded or not

func (*Yamc) GetGjsonString

func (y *Yamc) GetGjsonString(path string) (jsonStr string, err error)

GetGjsonString returns the content of the path as json string result or the error while processing the data

func (*Yamc) GetOrSet

func (y *Yamc) GetOrSet(key string, data interface{}) (interface{}, bool)

GetOrSet is just a wrapper for the sync.Map LoadOrStore function The bool result is true if the value was loaded, false if stored.

func (*Yamc) GetSourceDataType

func (y *Yamc) GetSourceDataType() int

GetSourceDataType returns the flag what tells us how the sourece was stuctured yamc.TYPE_ARRAY = []interface{} yamc.TYPE_STRING_MAP = map[string]interface{} yamc.UNSET = current nothing is loaded. so we have no type

func (*Yamc) Gjson

func (y *Yamc) Gjson(path string) (gjson.Result, error)

Gson wrapps gson and rerurns the gsonResult or the error while using Marshall the data into json what can be used by gson

func (*Yamc) IsLoaded

func (y *Yamc) IsLoaded() bool

IsLoaded returns the loaded flag. this is not the same as having data it just means it is read without having errors

func (*Yamc) Parse

func (y *Yamc) Parse(use DataReader, in []byte) error

Parse is wrapping the Unmarshal for json and yaml. because the default format is map[string]interface{} it fallback to read []interface{} and convert them.

func (*Yamc) Range

func (y *Yamc) Range(f func(key, value interface{}) bool)

Range is just a wrapper for the sync.Map Range function

func (*Yamc) Reset

func (y *Yamc) Reset()

Resets the whole Ymac

func (*Yamc) SetData

func (y *Yamc) SetData(data map[string]interface{})

setData reset current data and set new data by apply the map[string]interface{} to the sync.Map

func (*Yamc) Store

func (y *Yamc) Store(key string, data interface{})

Store is just a wrapper for the sync.Map Store function

func (*Yamc) ToString

func (y *Yamc) ToString(use DataReader) (str string, err error)

ToString uses the reader to create the string output of the data content

func (*Yamc) Update

func (y *Yamc) Update(key string, f func(value interface{}) interface{}) bool

Update is just a wrapper for the sync.Map Load and Store function it is a helper to update the data in the sync.Map and lock the sync.Map for the time of the update

type YamlReader

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

YamlReader is a reader for yaml files

func NewYamlReader

func NewYamlReader() *YamlReader

NewYamlReader creates a new YamlReader

func (*YamlReader) FileDecode

func (y *YamlReader) FileDecode(path string, decodeInterface interface{}) (err error)

FileDecode decodes a yaml file into a struct

func (*YamlReader) GetFields

func (y *YamlReader) GetFields() *StructDef

GetFields returns the field information

func (*YamlReader) HaveFields

func (y *YamlReader) HaveFields() bool

HaveFields returns true if the reader has field information

func (*YamlReader) Marshal

func (y *YamlReader) Marshal(in interface{}) (out []byte, err error)

Marshal marshals like yaml.Marshal

func (*YamlReader) SupportsExt

func (y *YamlReader) SupportsExt() []string

func (*YamlReader) Unmarshal

func (y *YamlReader) Unmarshal(in []byte, out interface{}) (err error)

Unmarshal unmarshals like yaml.Unmarshal

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

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