go-unic is a reliable, highβperformance Go library for parsing, serialising, and working with configuration files in the Universal Configuration Format (UNIC). UNIC combines readability, a minimalistic syntax, and flexibility β letting you describe complex data structures as easily as with JSON or YAML, but with a syntax that feels more natural for humans.
- Features
- Installation
- Quick Start
- UNIC Syntax
- Struct Tags (options)
- Examples
- Comparison with other formats
- Contributing
- License
- Humanβreadable syntax β intuitive, without superfluous symbols (like Nginx or HCL).
- Support for the major Go field types: structs, slices, maps, scalars (numbers, strings, booleans).
- Flexible tagβbased control β set field names, default values, omit empty fields, attributes, comments.
- Structure merging β automatically combine fields when serialising multiple objects with the same key.
- Arbitrary nesting depth β blocks, lists, and maps can be combined freely.
- Support for
anyinterfaces β deserialise intomap[string]anyor[]anywhen needed. - High performance β minimal reflection usage, cached struct metadata.
- Zero allocations during parsing (uses a
bbbuffer).
go get -u go.osspkg.com/unic@latestTo convert UNIC data into a Go struct, use unic.Unmarshal:
package main
import (
"fmt"
"os"
"go.osspkg.com/unic"
)
type Config struct {
LogLevel int `unic:"log_level,default=1"`
Port int `unic:"port"`
Features []string `unic:"features,omitempty"`
}
func main() {
data, err := os.ReadFile("config.unic")
if err != nil {
fmt.Printf("Error reading file: %v\n", err)
return
}
var cfg Config
if err := unic.Unmarshal(data, &cfg); err != nil {
fmt.Printf("Parsing error: %v\n", err)
return
}
fmt.Printf("Configuration: log_level=%d, port=%d, features=%v\n",
cfg.LogLevel, cfg.Port, cfg.Features)
}To write a struct to UNIC format, use unic.Marshal:
package main
import (
"fmt"
"os"
"go.osspkg.com/unic"
)
func main() {
cfg := Config{
LogLevel: 2,
Port: 8080,
Features: []string{"auth", "metrics"},
}
data, err := unic.Marshal(cfg)
if err != nil {
fmt.Printf("Serialisation error: %v\n", err)
return
}
if err := os.WriteFile("config.unic", data, 0644); err != nil {
fmt.Printf("Write error: %v\n", err)
}
}Marshal accepts a struct or a pointer to a struct. Maps, slices, and scalar
values are supported as fields; a map cannot be passed as the top-level value.
UNIC is a text file containing fields, blocks, lists, and maps. Basic rules:
| Construct | Example | Description |
|---|---|---|
| Field | key value; |
Assigns a scalar value (string, number, boolean). |
| Block | key { field1 val1; field2 val2; } |
Groups fields (similar to a struct). |
| List | key [val1, val2, val3]; |
Ordered collection of values. |
| Map | key (key1, val1, key2, val2); |
Keyβvalue pairs (keys are always strings). |
| Attributes | key attr1 attr2 { ... } |
Values before an opening block brace become struct attributes. |
To avoid conflicts with system characters ({}[]();,#), spaces, quotes, or line breaks, the following rules apply:
- If the string contains
",{,},[,],(,),#,;,,or spaces β enclose it in single quotes:'hello "world"'. - If the string contains
',{,},[,],(,),#,;,,or spaces β enclose it in double quotes:"hello 'world'". - If the string contains both
'and"as well as special characters or line breaks β use triple backticks:```hello 'world' "foo"```.
Input must be valid UTF-8. Invalid UTF-8 sequences are rejected with a parse error.
Example:
message 'Hello, "friend"!';
path "C:\\Program Files\\App";
multiline ```first line
second line```;
- Singleβline β after
;on the same line:port 80; # standard port - Block β after
{on the same line (applies to the entire block):server { # server settings host '127.0.0.1'; }
If values are given before an opening block brace, they are interpreted as struct attributes. The attr=N tag sets the ordinal number (starting from 1).
Example:
server web 80 { host 'localhost'; }
corresponds to the struct:
package main
type Server struct {
Tag string `unic:"tag,attr=1"` // "web"
Port int `unic:"port,attr=2"` // 80
Host string `unic:"host"`
}The unic tag has the format: unic:"name[,option1='value'][,option2='value']..."
Available options:
| Option | Description |
|---|---|
name |
Field name in the configuration (mandatory). |
default |
Default value (for scalars or lists separated by ;). |
omitempty |
If the field is empty (zero value), it is omitted during serialisation and ignored on parse. |
attr |
Ordinal number of a block attribute (number > 0). |
desc |
Comment added during serialisation. |
Example:
package main
type Config struct {
LogLevel int `unic:"log_level,default=1,desc='log level'"`
Servers []Server `unic:"server"`
}
type Server struct {
Name string `unic:"name,attr=1"`
Port int `unic:"port"`
}Configuration:
log_level 1;
servers {
server web {
port 80;
host 'localhost';
}
server admin {
port 8080;
host '127.0.0.1';
auth (user1, passwd1, user2, passwd2);
}
}
Go struct:
package main
type Config struct {
LogLevel int `unic:"log_level"`
Servers struct {
Servers []struct {
Name string `unic:"name,attr=1"`
Port int `unic:"port"`
Host string `unic:"host"`
Auth map[string]string `unic:"auth,omitempty"`
} `unic:"server"`
} `unic:"servers"`
}unic.Marshal accepts several struct arguments β all are written into one
document. Equal repeated scalar values are deduplicated, while struct values
without attributes and with the same name are combined into one block. Maps,
slices, and values with attributes are not structurally merged.
package main
type Part1 struct {
Common string `unic:"common"`
A int `unic:"a"`
}
type Part2 struct {
Common string `unic:"common"`
B bool `unic:"b"`
}
data, _ := unic.Marshal(Part1{Common:"shared", A:42}, Part2{Common:"shared", B:true})
// Output:
// common shared;
// a 42;
// b true;| Format | Readability | Complex structures | Comments | Performance (Go) |
|---|---|---|---|---|
| UNIC | β β β β β | β β β β β | β | High (reflection with cache) |
| JSON | β β β ββ | β β β β β | β | Very high |
| YAML | β β β β β | β β β β β | β | Medium |
| TOML | β β β β β | β β β ββ | β | Medium |
| HCL | β β β β β | β β β β β | β | Medium |
UNIC offers the best balance between readability and performance, especially if you need comments, attributes, and flexible struct merging.
We welcome your ideas and improvements! To contribute:
- Fork the repository.
- Create a branch for your feature (
git checkout -b feature/amazing-feature). - Make your changes and write tests.
- Ensure all linters and tests pass (
make pre-commit). - Open a pull request.
Distributed under the BSD 3βClause License. See the LICENSE file for details.