lexkey is a lightweight lexicographically sortable key encoding library for Go.
It provides consistent, ordered, and efficient encoding for common data types so they sort correctly in byte-wise order.
- π Lexicographically sortable encoding for structured keys.
- π Supports strings, integers, floats, UUIDs, booleans, timestamps, durations, and byte slices.
- π Consistent ordering for mixed types like
int32andint64. - π¦ Optimized encoding for space efficiency and speed.
- π‘ JSON serialization support for interoperability.
go get github.com/fgrzl/lexkeyGuides: docs/ β overview, getting started. Wire format: SPEC.md.
key := lexkey.Encode("user", 123, true)
fmt.Println("Encoded Key (Hex):", key.ToHexString())key1 := lexkey.Encode("apple")
key2 := lexkey.Encode("banana")
fmt.Println(string(key1) < string(key2)) // β
True (correct lexicographic order)key1 := lexkey.Encode(int64(-100))
key2 := lexkey.Encode(int64(50))
fmt.Println(string(key1) < string(key2)) // β
True (correct sorting for signed integers)import "github.com/google/uuid"
id := uuid.New()
key := lexkey.Encode("order", id)
fmt.Println("Encoded UUID Key:", key.ToHexString())import "encoding/json"
key := lexkey.Encode("session", 42)
jsonData, _ := json.Marshal(key)
fmt.Println(string(jsonData)) // β
Encoded as a hex string| Type | Supported? | Encoding Details |
|---|---|---|
string |
β Yes | Stored as raw UTF-8 bytes |
int32 |
β Yes | Canonicalized to int64 for uniform sorting |
int64 |
β Yes | Sign-bit flipped for correct ordering |
uint32 |
β Yes | Big-endian encoded |
uint64 |
β Yes | Big-endian encoded |
float32 |
β Yes | Canonicalized to float64 then transformed |
float64 |
β Yes | IEEE 754 encoded with sign-bit transformation |
bool |
β Yes | true β 0x01, false β 0x00 |
uuid.UUID |
β Yes | 16-byte raw representation |
[]byte |
β Yes | Stored as-is |
time.Time |
β Yes | Encoded as int64 nanoseconds since Unix epoch |
time.Duration |
β Yes | Encoded as int64 nanoseconds |
func Encode(parts ...any) LexKey
func NewLexKey(parts ...any) (LexKey, error)
func EncodeInto(dst []byte, parts ...any) (int, error)
func EncodeSize(parts ...any) intNotes:
- As of 2025-10-01, numeric width canonicalization is the default (breaking change):
- int, int8, int16, int32 β int64
- uint8, uint16, uint32 β uint64
- float32 β float64
- For explicit use, the following helpers are provided (equivalent to default behavior):
- EncodeCanonicalWidth / NewLexKeyCanonicalWidth / EncodeIntoCanonicalWidth / EncodeSizeCanonicalWidth
func EncodeFirst(parts ...any) LexKey // lower bound: prefix + 0x00 (sorts before any extension of the prefix)
func EncodeLast(parts ...any) LexKey // upper bound: prefix + 0xFF (sorts after any extension of the prefix)
func Compare(a, b LexKey) int // -1/0/1 without allocationsPrefix scans:
// To scan all keys with a given prefix, use a half-open range [lower, upper):
lower := lexkey.EncodeFirst("tenant", "users") // ... 00
upper := lexkey.EncodeLast("tenant", "users") // ... ff
// All keys that start with ("tenant", "users", ...) will satisfy: lower <= key && key < upperfunc (e LexKey) ToHexString() string
func (e *LexKey) FromHexString(hexStr string) errorfunc (e LexKey) MarshalJSON() ([]byte, error)
func (e *LexKey) UnmarshalJSON(data []byte) errorβ
Fast & Efficient β Uses compact, binary-safe encoding.
β
Correct Ordering β Works across all supported types.
β
Minimal Dependencies β Only uuid and standard Go packages.
Run the full test suite:
go test -cover ./...Numeric width canonicalization is now the default. This ensures logical numeric ordering across widths (e.g., uint32(1) and uint64(1) are equal on the wire). If you require the previous native-width bytes, pin an older release or re-encode using legacy rules as described in SPEC.md.
See SPEC.md for the full encoding specification and test vectors.
Test Coverage: β 99.6% π―