empaths

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jan 17, 2026 License: MIT Imports: 5 Imported by: 0

README

empaths

Go Reference CI Go Report Card

empaths (em-paths) is a lightweight, performant Go library for accessing nested values in data structures using string path expressions. The "em" stands for "Model" — think of it as "model paths."

Features

  • Simple path syntax — Access deeply nested fields with intuitive dot notation
  • Expression concatenation — Combine multiple values and literals into formatted strings
  • Universal data access — Works with structs, maps, slices, arrays, and pointers
  • Zero dependencies — Pure Go standard library, no external dependencies
  • Performant — Optimized for minimal allocations and fast execution
  • Type-safe — Graceful handling of nil values and type mismatches
  • Extensible — Support for custom reference resolvers

Installation

go get github.com/authentic-devel/empaths

Requires Go 1.21 or later.

Quick Start

package main

import (
    "fmt"
    "github.com/authentic-devel/empaths"
)

type User struct {
    Name    string
    Age     int
    Address struct {
        City    string
        Country string
    }
    Tags []string
}

func main() {
    user := User{
        Name: "Alice",
        Age:  30,
        Address: struct {
            City    string
            Country string
        }{City: "New York", Country: "USA"},
        Tags: []string{"developer", "gopher"},
    }

    // Access nested fields
    name := empaths.Resolve(".Name", user, nil)
    fmt.Println(name) // "Alice"

    city := empaths.Resolve(".Address.City", user, nil)
    fmt.Println(city) // "New York"

    // Access slice elements
    firstTag := empaths.Resolve(".Tags[0]", user, nil)
    fmt.Println(firstTag) // "developer"

    // Concatenate multiple expressions into a string
    greeting := empaths.Resolve("'Hello, ' .Name '! You are ' .Age ' years old.'", user, nil)
    fmt.Println(greeting) // "Hello, Alice! You are 30 years old."
}

Path Syntax

Expression Concatenation

A path can contain multiple expressions separated by spaces. When multiple expressions are present, each is evaluated and their string representations are concatenated into a single result:

// Single expression → returns the value directly (preserves type)
empaths.Resolve(".Age", user, nil)  // returns int: 30

// Multiple expressions → concatenated as strings
empaths.Resolve(".Name ' is ' .Age ' years old'", user, nil)
// returns string: "Alice is 30 years old"

empaths.Resolve("'User: ' .Name", user, nil)
// returns string: "User: Alice"

empaths.Resolve(".FirstName ' ' .LastName", user, nil)
// returns string: "John Doe"

This is powerful for building dynamic strings from multiple data sources:

// Build a greeting
empaths.Resolve("'Hello, ' .Name '! Welcome to ' .Address.City '.'", user, nil)
// → "Hello, Alice! Welcome to New York."

// Combine with external references
empaths.Resolve(":greeting ', ' .Name '!'", user, resolver)
// → "Hello, Alice!"

Note: When a path contains only a single expression, the original type is preserved. When multiple expressions are present, the result is always a string.

Field Access

Use dot notation to access struct fields or map keys:

".Name"              // Access field "Name"
".User.Address.City" // Access nested fields
".Data.key"          // Access map with string key
Array/Slice Indexing

Use bracket notation with zero-based indices:

".Items[0]"          // First element
".Items[2]"          // Third element
".Matrix[0][1]"      // Nested array access
".Users[0].Name"     // Field of array element
Map Access

Maps support both dot and bracket notation:

".Config.timeout"    // Dot notation (string keys)
".Config[timeout]"   // Bracket notation
".Scores[42]"        // Integer key
".Flags[true]"       // Boolean key
String Literals

Embed literal strings in expressions:

"'Hello'"                    // Single quotes
"\"World\""                  // Double quotes
"'Hello, ' .Name '!'"        // Concatenation → "Hello, Alice!"
"'It\\'s working'"           // Escaped quotes
Comparisons

Compare values using == or !=:

"?.Age=='30'"                // Equals comparison → true/false
"?.Status!='inactive'"       // Not equals comparison
"?.Name==.ExpectedName"      // Compare two fields
Negation

Negate boolean values with !:

"!.IsActive"                 // Negate boolean field
"!'true'"                    // Negate literal → false
External References

Resolve custom references with a resolver function:

resolver := func(name string, data any) any {
    switch name {
    case "greeting":
        return "Hello"
    case "config":
        return someConfig
    }
    return nil
}

result := empaths.Resolve(":greeting ', ' .Name", user, resolver)
// → "Hello, Alice"

Method Calls

Zero-argument methods can be called as part of a path:

type User struct {
    FirstName string
    LastName  string
}

func (u User) FullName() string {
    return u.FirstName + " " + u.LastName
}

// Usage
result := empaths.Resolve(".FullName", user, nil)
// → "John Doe"

Working with Different Types

Structs
type Person struct {
    Name string
    Age  int
}

person := Person{Name: "Bob", Age: 25}
empaths.Resolve(".Name", person, nil) // "Bob"
empaths.Resolve(".Age", person, nil)  // 25
Maps
data := map[string]any{
    "name": "Charlie",
    "scores": map[string]int{
        "math":    95,
        "science": 88,
    },
}

empaths.Resolve(".name", data, nil)          // "Charlie"
empaths.Resolve(".scores.math", data, nil)   // 95
empaths.Resolve(".scores[science]", data, nil) // 88
Slices and Arrays
items := []string{"apple", "banana", "cherry"}

empaths.Resolve(".[0]", items, nil) // "apple"
empaths.Resolve(".[2]", items, nil) // "cherry"
empaths.Resolve(".[99]", items, nil) // nil (out of bounds)
Pointers

Pointers are automatically dereferenced:

user := &User{Name: "Diana"}
empaths.Resolve(".Name", user, nil) // "Diana"
Nil Safety

The library handles nil values gracefully:

var user *User = nil
empaths.Resolve(".Name", user, nil)      // nil (no panic)
empaths.Resolve(".NonExistent", data, nil) // nil
empaths.Resolve(".Items[999]", data, nil)  // nil

API Reference

Resolve
func Resolve(path string, data any, refResolver ReferenceResolver) any

Evaluates a path expression against a data model and returns the resolved value.

Parameters:

  • path — The path expression to evaluate
  • data — The data model to evaluate against
  • refResolver — Optional function to resolve external references (can be nil)

Returns: The resolved value, or nil if the path cannot be resolved.

ReferenceResolver
type ReferenceResolver func(name string, data any) any

Function type for resolving external references (paths starting with :).

Error Handling

empaths uses graceful failure — invalid paths return nil rather than panicking or returning errors. This design simplifies usage in templates and other contexts where nil is an acceptable fallback.

// All of these return nil without panicking:
empaths.Resolve(".NonExistent", data, nil)
empaths.Resolve(".Items[999]", data, nil)
empaths.Resolve("invalid path", data, nil)
empaths.Resolve(".Field", nil, nil)

Character Encoding

Path expressions should use ASCII characters only for path syntax elements (field names, operators, brackets, quotes). The parser is optimized for ASCII and processes paths byte-by-byte rather than as Unicode code points.

What works:

  • ASCII field/method names: .User.Name, .GetValue
  • ASCII map keys: .Config[timeout], .Data["key"]
  • UTF-8 content in string literals: 'こんにちは', "日本語"
  • UTF-8 values in your data structures (these are not affected)

What to avoid:

  • Non-ASCII field names in paths: .用户.名前 (undefined behavior)
  • Non-ASCII map keys in bracket notation: .Data[キー] (undefined behavior)
  • Non-ASCII reference names: :配置 (undefined behavior)
// Safe: ASCII path syntax, UTF-8 data values
data := map[string]string{"greeting": "こんにちは"}
empaths.Resolve(".greeting", data, nil)  // Returns "こんにちは"

// Safe: UTF-8 in string literals
empaths.Resolve("'Hello ' .Name '!'", user, nil)

// Undefined: Non-ASCII in path syntax
type User struct {
    名前 string
}
empaths.Resolve(".名前", user, nil)  // May work but not guaranteed

Use Cases

  • Template engines — Dynamic value resolution in templates
  • Configuration — Access nested config values by path
  • API responses — Extract values from JSON-decoded maps
  • Testing — Assert on deeply nested values
  • Data transformation — Map values between structures

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package empaths provides xpath-like path resolution for Go data structures.

Empaths (pronounced "em-paths" - the M stands for Model) allows reflective access to nested values in structs, maps, slices, and arrays using a simple path syntax. It is designed to be fast, memory-efficient, and easy to use.

Path Syntax

Path expressions can contain multiple segments that are evaluated in sequence:

Model References (start with '.'):

.Name              - Access a struct field or map key named "Name"
.User.Address.City - Access nested fields
.Users[0]          - Access array/slice element by index (zero-based)
.Data["key"]       - Access map element by key
.GetValue          - Call a zero-argument method

String Literals (enclosed in quotes):

'Hello'            - Single-quoted string
"World"            - Double-quoted string
'It\'s'            - Escaped quotes within strings

Negation (starts with '!'):

!.IsActive         - Negate a boolean value
!'true'            - Negate a string "true" -> false

Comparisons (start with '?'):

?.Age=='18'        - Compare if Age equals 18
?.Status!='active' - Compare if Status is not "active"

External References (start with ':'):

:config            - Resolve using the provided ReferenceResolver

Multiple segments can be combined:

'Hello, ' .User.Name '!'  - Concatenates to "Hello, John!"

Array and Slice Access

Arrays and slices are accessed using zero-based integer indices:

.Items[0]          - First element
.Items[1]          - Second element
.Matrix[0][1]      - Nested array access

Out-of-bounds access returns nil rather than panicking. Negative indices are not supported.

Map Access

Maps can be accessed either with bracket notation or dot notation:

.Data["key"]       - Bracket notation (works for any key type)
.Data.key          - Dot notation (string keys only)

The library supports maps with various key types:

  • string, int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • bool, float32, float64

Method Calls

Zero-argument methods can be called as part of a path:

.GetFullName       - Calls GetFullName() method
.User.String       - Calls String() on User

Methods must:

  • Take no arguments
  • Return at least one value (first value is used)

Error Handling

The library uses graceful failure - invalid paths return nil rather than panicking or returning errors. This design choice simplifies usage in templates and other contexts where nil is an acceptable fallback.

Example Usage

type User struct {
    Name    string
    Age     int
    Address struct {
        City string
    }
}

user := User{
    Name: "Alice",
    Age:  30,
    Address: struct{ City string }{City: "NYC"},
}

// Simple field access
name := empaths.Resolve(".Name", user, nil)  // "Alice"

// Nested field access
city := empaths.Resolve(".Address.City", user, nil)  // "NYC"

// String concatenation
greeting := empaths.Resolve("'Hello, ' .Name '!'", user, nil)  // "Hello, Alice!"

// Comparison
isAdult := empaths.Resolve("?.Age=='30'", user, nil)  // true

Thread Safety

All functions in this package are safe for concurrent use. The library does not maintain any global state.

Package empaths provides xpath-like path resolution for Go data structures.

Empaths (pronounced "em-paths" - the M stands for Model) allows reflective access to nested values in structs, maps, slices, and arrays using a simple path syntax.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Resolve

func Resolve(path string, data any, refResolver ReferenceResolver) any

Resolve evaluates a path expression against a data model and returns the resolved value.

A path can consist of multiple segments and supports various expression types:

  • Model references: Starts with '.' followed by field/property names (e.g., ".User.Name")
  • String literals: Enclosed in single or double quotes (e.g., "'Hello'" or "\"World\"")
  • Negation: Starts with '!' to negate a boolean value (e.g., "!.IsActive")
  • External references: Starts with ':' followed by reference name (e.g., ":config")
  • Comparisons: Starts with '?' followed by operands and operator (e.g., "?.Age=='18'")

Character encoding: Path syntax elements (field names, map keys, reference names) should use ASCII characters only. UTF-8 content within string literals is supported, but non-ASCII characters in path syntax have undefined behavior.

Path segments can be combined to form complex expressions, and can include:

  • Nested properties: ".User.Address.City"
  • Array/slice indexing: ".Users[0].Name"
  • Map access: ".Data[\"key\"]" or ".Data.key"
  • Method calls: ".User.GetFullName"

Examples:

  • ".User.Name" - Accesses the Name property of the User object
  • "'Hello ' .User.Name" - Concatenates the string "Hello " with User.Name
  • "?.IsAdmin=='true'" - Compares if IsAdmin equals true
  • "!.IsBlocked" - Negates the IsBlocked boolean value
  • ":config" - References an external value named "config"

Parameters:

  • path: The path expression to evaluate
  • data: The data model to evaluate the path against
  • referenceResolver: Optional function to resolve external references (prefixed with ':')

Returns:

The resolved value from the data model based on the path expression

func ResolveModel

func ResolveModel(path string, data any, index int) (any, int, error)

ResolveModel resolves a model reference in a path expression. Model references start with '.' followed by a path to a property or method in the data model. This function can be used directly to resolve a model path against a data object.

A model path can include:

  • Simple property access: ".PropertyName"
  • Nested property access: ".User.Address.City"
  • Array/slice indexing: ".Users[0]"
  • Map access: ".Data[\"key\"]" or ".Data.key"
  • Method calls: ".GetFullName"

Parameters:

  • path: The path expression string
  • data: The data model to evaluate against
  • index: The current index in the path (should point to the '.' character)

Returns:

  • The resolved value from the data model
  • The new index after processing
  • Error if the path cannot be resolved

Types

type ReferenceResolver

type ReferenceResolver func(name string, data any) any

ReferenceResolver is a function type that resolves external references. It takes a reference name and a data context, and returns the resolved value. This can be used to resolve references to templates, configuration values, or any other external data sources.

Jump to

Keyboard shortcuts

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