A high-performance parser and encoder for the VDF (Valve Data Format) in Go. VDF is commonly used in Valve games like Counter-Strike, Dota 2, and Team Fortress 2 for configuration files, item definitions, and game data.
- ✅ Complete VDF Support: Parse and encode VDF files with full feature support
- ✅ Binary VDF Support: Parse and encode binary VDF (Valve's binary KeyValues format)
- ✅ Struct Mapping: Direct unmarshaling to Go structs with
vdftags - ✅ Node Tree API: Work with VDF data as a tree of nodes
- ✅ Comment Preservation: Maintains head and line comments during parsing
- ✅ Position Tracking: Line and column information for error reporting
- ✅ Custom Marshalers: Implement
MarshalVDFandUnmarshalVDFinterfaces - ✅ JSON Compatibility: Nodes can be marshaled/unmarshaled to/from JSON
- ✅ High Performance: Optimized with efficient parsing and minimal allocations
- ✅ Concurrent Safe: Thread-safe operations with proper synchronization
- ✅ Escaped Quote Support: Properly handles escaped quotes in string values
- ✅ Robust Error Handling: Detailed error messages with line/column information
- ✅ Clean Architecture: Well-structured, maintainable code with comprehensive tests
go get github.com/lewisgibson/go-vdfpackage main
import (
"fmt"
"log"
govdf "github.com/lewisgibson/go-vdf"
)
func main() {
vdfData := []byte(`
"items_game"
{
"game_info"
{
"first_valid_class" "2"
"last_valid_class" "3"
"max_num_stickers" "5"
}
}`)
// Parse into a Node tree
var node govdf.Node
if err := govdf.Unmarshal(vdfData, &node); err != nil {
log.Fatal(err)
}
// Access nested values
gameInfo := node.Children["items_game"].Children["game_info"]
fmt.Printf("First valid class: %s\n", gameInfo.Children["first_valid_class"].Value)
}type GameInfo struct {
FirstValidClass string `vdf:"first_valid_class"`
LastValidClass string `vdf:"last_valid_class"`
MaxNumStickers int `vdf:"max_num_stickers"`
}
type ItemsGame struct {
GameInfo GameInfo `vdf:"game_info"`
}
// Parse directly into structs
var itemsGame ItemsGame
if err := govdf.Unmarshal(vdfData, &itemsGame); err != nil {
log.Fatal(err)
}
fmt.Printf("Max stickers: %d\n", itemsGame.GameInfo.MaxNumStickers)// Create a Node tree
node := &govdf.Node{
Type: govdf.NodeTypeMap,
Children: map[string]*govdf.Node{
"player": {
Type: govdf.NodeTypeMap,
Children: map[string]*govdf.Node{
"name": {Type: govdf.NodeTypeScalar, Value: "John Doe"},
"level": {Type: govdf.NodeTypeScalar, Value: "42"},
},
},
},
}
// Encode to VDF
vdfBytes, err := govdf.Marshal(node)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(vdfBytes))// Decode binary VDF data
var node govdf.Node
if err := govdf.UnmarshalBinary(binaryData, &node); err != nil {
log.Fatal(err)
}
// Decode into a struct
type AppInfo struct {
AppID string `vdf:"appid"`
Name string `vdf:"name"`
}
type Root struct {
AppInfo AppInfo `vdf:"appinfo"`
}
var root Root
if err := govdf.UnmarshalBinary(binaryData, &root); err != nil {
log.Fatal(err)
}
// Encode to binary VDF
data, err := govdf.MarshalBinary(&node)
if err != nil {
log.Fatal(err)
}type Player struct {
Name string
Level int
}
func (p Player) MarshalVDF() ([]byte, error) {
return govdf.Marshal(&govdf.Node{
Type: govdf.NodeTypeMap,
Children: map[string]*govdf.Node{
"name": {Type: govdf.NodeTypeScalar, Value: p.Name},
"level": {Type: govdf.NodeTypeScalar, Value: fmt.Sprintf("%d", p.Level)},
},
})
}
func (p *Player) UnmarshalVDF(node *govdf.Node) error {
if nameNode, ok := node.Children["name"]; ok {
p.Name = nameNode.Value
}
if levelNode, ok := node.Children["level"]; ok {
level, err := strconv.Atoi(levelNode.Value)
if err != nil {
return err
}
p.Level = level
}
return nil
}Benchmark results on AMD Ryzen 9 9950X3D:
BenchmarkUnmarshal_SimpleStruct-32 459,431 ops/sec 2,179 ns/op 5,296 B/op 18 allocs/op
BenchmarkUnmarshal_ComplexStruct-32 192,668 ops/sec 5,190 ns/op 8,424 B/op 71 allocs/op
BenchmarkUnmarshal_Node-32 402,666 ops/sec 2,484 ns/op 5,816 B/op 31 allocs/op
BenchmarkMarshal_SimpleStruct-32 1,477,711 ops/sec 677 ns/op 625 B/op 24 allocs/op
BenchmarkMarshal_ComplexStruct-32 319,405 ops/sec 3,131 ns/op 2,989 B/op 134 allocs/op
BenchmarkMarshal_Node-32 1,290,747 ops/sec 775 ns/op 432 B/op 50 allocs/op
BenchmarkUnmarshalBinary_Simple-32 487,994 ops/sec 2,049 ns/op 5,344 B/op 21 allocs/op
BenchmarkUnmarshalBinary_Complex-32 91,996 ops/sec 10,870 ns/op 18,712 B/op 198 allocs/op
BenchmarkUnmarshalBinary_Struct-32 348,796 ops/sec 2,868 ns/op 6,400 B/op 28 allocs/op
BenchmarkMarshalBinary_Simple-32 3,084,447 ops/sec 324 ns/op 128 B/op 13 allocs/op
BenchmarkMarshalBinary_Complex-32 198,492 ops/sec 5,038 ns/op 2,797 B/op 218 allocs/op
BenchmarkMarshalBinary_Struct-32 1,392,251 ops/sec 719 ns/op 653 B/op 17 allocs/op
Use the provided Makefile target for benchmarking:
make benchUnmarshal(data []byte, v any) error- Parse VDF data into a struct or NodeMarshal(v any) ([]byte, error)- Encode a struct or Node to VDF formatNewDecoder(r io.Reader) *Decoder- Create a streaming decoderNewEncoder(w io.Writer) *Encoder- Create a streaming encoderUnmarshalBinary(data []byte, v any) error- Parse binary VDF data into a struct or NodeMarshalBinary(v any) ([]byte, error)- Encode a struct or Node to binary VDF formatNewBinaryDecoder(r io.Reader) *BinaryDecoder- Create a streaming binary decoderNewBinaryEncoder(w io.Writer) *BinaryEncoder- Create a streaming binary encoder
type Node struct {
Type NodeType // NodeTypeMap or NodeTypeScalar
Value string // Value for scalar nodes
Children map[string]*Node // Child nodes for map nodes
HeadComment string // Comment before the node
LineComment string // Comment on the same line
Line int // Line number in source
Column int // Column number in source
}Marshaler- ImplementMarshalVDF() ([]byte, error)for custom encodingUnmarshaler- ImplementUnmarshalVDF(*Node) errorfor custom decoding
See the examples directory for more detailed usage examples.
This project is licensed under the MIT License - see the LICENSE.md file for details.