Files
bifrost/core/schemas/orderedmap.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

635 lines
16 KiB
Go

package schemas
import (
"bytes"
"encoding/json"
"fmt"
"sort"
)
// OrderedMap is a map that preserves insertion order of keys.
// It stores key-value pairs and maintains the order in which keys were first inserted.
// It is NOT safe for concurrent use.
type OrderedMap struct {
keys []string
values map[string]interface{}
}
// Pair is a key-value pair for constructing OrderedMaps with order preserved.
type Pair struct {
Key string
Value interface{}
}
// KV is a shorthand constructor for Pair.
func KV(key string, value interface{}) Pair {
return Pair{Key: key, Value: value}
}
// NewOrderedMap creates a new empty OrderedMap.
func NewOrderedMap() *OrderedMap {
return &OrderedMap{
values: make(map[string]interface{}),
}
}
// NewOrderedMapWithCapacity creates a new empty OrderedMap with preallocated capacity.
func NewOrderedMapWithCapacity(cap int) *OrderedMap {
return &OrderedMap{
keys: make([]string, 0, cap),
values: make(map[string]interface{}, cap),
}
}
// NewOrderedMapFromPairs creates an OrderedMap from key-value pairs, preserving the given order.
func NewOrderedMapFromPairs(pairs ...Pair) *OrderedMap {
om := &OrderedMap{
keys: make([]string, 0, len(pairs)),
values: make(map[string]interface{}, len(pairs)),
}
for _, p := range pairs {
om.Set(p.Key, p.Value)
}
return om
}
// OrderedMapFromMap creates an OrderedMap from a plain map.
// Key order is NOT guaranteed since Go maps have undefined iteration order.
// Use this only when insertion order doesn't matter (e.g., for hashing).
func OrderedMapFromMap(m map[string]interface{}) *OrderedMap {
if m == nil {
return nil
}
om := &OrderedMap{
keys: make([]string, 0, len(m)),
values: make(map[string]interface{}, len(m)),
}
for k, v := range m {
om.keys = append(om.keys, k)
om.values[k] = v
}
return om
}
// Get returns the value associated with the key and whether the key exists.
func (om *OrderedMap) Get(key string) (interface{}, bool) {
if om == nil {
return nil, false
}
v, ok := om.values[key]
return v, ok
}
// Set sets the value for a key. If the key is new, it is appended to the end.
// If the key already exists, its value is updated in place without changing order.
func (om *OrderedMap) Set(key string, value interface{}) {
if om.values == nil {
om.values = make(map[string]interface{})
}
if _, exists := om.values[key]; !exists {
om.keys = append(om.keys, key)
}
om.values[key] = value
}
// Delete removes a key and its value. The key is also removed from the ordered keys list.
func (om *OrderedMap) Delete(key string) {
if om == nil {
return
}
if _, exists := om.values[key]; !exists {
return
}
delete(om.values, key)
for i, k := range om.keys {
if k == key {
om.keys = append(om.keys[:i], om.keys[i+1:]...)
break
}
}
}
// Len returns the number of key-value pairs.
func (om *OrderedMap) Len() int {
if om == nil {
return 0
}
return len(om.keys)
}
// Keys returns the keys in insertion order. The returned slice is a copy.
func (om *OrderedMap) Keys() []string {
if om == nil {
return nil
}
out := make([]string, len(om.keys))
copy(out, om.keys)
return out
}
// Range iterates over key-value pairs in insertion order.
// If fn returns false, iteration stops.
func (om *OrderedMap) Range(fn func(key string, value interface{}) bool) {
if om == nil {
return
}
for _, k := range om.keys {
if !fn(k, om.values[k]) {
break
}
}
}
// Clone creates a shallow copy of the OrderedMap (keys and top-level values are copied,
// but nested values share references).
func (om *OrderedMap) Clone() *OrderedMap {
if om == nil {
return nil
}
clone := &OrderedMap{
keys: make([]string, len(om.keys)),
values: make(map[string]interface{}, len(om.values)),
}
copy(clone.keys, om.keys)
for k, v := range om.values {
clone.values[k] = v
}
return clone
}
// ToMap returns a plain map[string]interface{} with the same key-value pairs.
// The returned map does not preserve insertion order.
func (om *OrderedMap) ToMap() map[string]interface{} {
if om == nil {
return nil
}
m := make(map[string]interface{}, len(om.values))
for k, v := range om.values {
m[k] = v
}
return m
}
// MarshalJSON serializes the OrderedMap to JSON, preserving insertion order of keys.
// Uses a value receiver so that both OrderedMap and *OrderedMap invoke this method
// (critical for []OrderedMap slices like AnyOf/OneOf/AllOf in ToolFunctionParameters).
func (om OrderedMap) MarshalJSON() ([]byte, error) {
if om.values == nil {
return []byte("null"), nil
}
var buf bytes.Buffer
buf.WriteByte('{')
for i, k := range om.keys {
if i > 0 {
buf.WriteByte(',')
}
// key
keyBytes, err := MarshalSorted(k)
if err != nil {
return nil, err
}
buf.Write(keyBytes)
buf.WriteByte(':')
// value — nested *OrderedMap values will use their own MarshalJSON
valBytes, err := MarshalSorted(om.values[k])
if err != nil {
return nil, err
}
buf.Write(valBytes)
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// MarshalSorted serializes the OrderedMap to JSON with keys sorted alphabetically.
// Use this when deterministic output is needed regardless of insertion order (e.g., hashing).
func (om *OrderedMap) MarshalSorted() ([]byte, error) {
if om == nil {
return []byte("null"), nil
}
keys := make([]string, len(om.keys))
copy(keys, om.keys)
sort.Strings(keys)
var buf bytes.Buffer
buf.WriteByte('{')
for i, k := range keys {
if i > 0 {
buf.WriteByte(',')
}
keyBytes, err := MarshalSorted(k)
if err != nil {
return nil, err
}
buf.Write(keyBytes)
buf.WriteByte(':')
valBytes, err := MarshalSorted(om.values[k])
if err != nil {
return nil, err
}
buf.Write(valBytes)
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// UnmarshalJSON deserializes JSON into the OrderedMap, preserving the key order
// from the JSON document. Nested objects are also deserialized as *OrderedMap.
// Note: uses encoding/json.Decoder (not sonic) because token-by-token decoding
// is required to preserve key order from the JSON document.
func (om *OrderedMap) UnmarshalJSON(data []byte) error {
// Handle null
trimmed := bytes.TrimSpace(data)
if bytes.Equal(trimmed, []byte("null")) {
om.keys = nil
om.values = nil
return nil
}
dec := json.NewDecoder(bytes.NewReader(data))
// Read opening brace
t, err := dec.Token()
if err != nil {
return fmt.Errorf("orderedmap: expected '{': %w", err)
}
delim, ok := t.(json.Delim)
if !ok || delim != '{' {
return fmt.Errorf("orderedmap: expected '{', got %v", t)
}
om.keys = om.keys[:0]
if om.values == nil {
om.values = make(map[string]interface{})
} else {
for k := range om.values {
delete(om.values, k)
}
}
for dec.More() {
// Read key
keyToken, err := dec.Token()
if err != nil {
return fmt.Errorf("orderedmap: reading key: %w", err)
}
key, ok := keyToken.(string)
if !ok {
return fmt.Errorf("orderedmap: expected string key, got %T", keyToken)
}
// Read value, preserving nested object order
value, err := decodeOrderedValue(dec)
if err != nil {
return fmt.Errorf("orderedmap: reading value for key %q: %w", key, err)
}
om.Set(key, value)
}
// Read closing brace
if _, err := dec.Token(); err != nil {
return fmt.Errorf("orderedmap: expected '}': %w", err)
}
return nil
}
// jsonSchemaPriority maps JSON Schema keywords to their preferred
// serialization position. Keys present in this map are emitted first
// (in the given order), followed by all remaining keys alphabetically.
// This matches the optimal ordering for LLM tool schemas: the model
// sees type and description before properties, constraints, etc.
var jsonSchemaPriority = map[string]int{
"type": 0,
"description": 1,
"properties": 2,
"required": 3,
}
// SortKeys sorts the keys of this OrderedMap using JSON Schema priority
// ordering (type, description, properties, required first), with remaining
// keys sorted alphabetically. Nested *OrderedMap values are also sorted
// recursively.
func (om *OrderedMap) SortKeys() {
if om == nil || len(om.keys) == 0 {
return
}
sort.Slice(om.keys, func(i, j int) bool {
pi, okI := jsonSchemaPriority[om.keys[i]]
pj, okJ := jsonSchemaPriority[om.keys[j]]
switch {
case okI && okJ:
return pi < pj
case okI:
return true
case okJ:
return false
default:
return om.keys[i] < om.keys[j]
}
})
for k, v := range om.values {
switch nested := v.(type) {
case *OrderedMap:
nested.SortKeys()
case map[string]interface{}:
converted := OrderedMapFromMap(nested)
converted.SortKeys()
om.values[k] = converted
case []interface{}:
sortOrderedMapsInSlice(nested)
}
}
}
func sortOrderedMapsInSlice(s []interface{}) {
for i, item := range s {
switch v := item.(type) {
case *OrderedMap:
v.SortKeys()
case map[string]interface{}:
converted := OrderedMapFromMap(v)
converted.SortKeys()
s[i] = converted
case []interface{}:
sortOrderedMapsInSlice(v)
}
}
}
// SortedCopy returns a new OrderedMap with keys sorted using JSON Schema
// priority ordering. Nested *OrderedMap values are recursively copied and
// sorted. Primitive values (strings, numbers, bools) are shared, not cloned.
// This is much cheaper than a full JSON marshal/unmarshal Clone because it
// only allocates new key slices and value maps.
func (om *OrderedMap) SortedCopy() *OrderedMap {
if om == nil {
return nil
}
if len(om.keys) == 0 {
return &OrderedMap{values: make(map[string]interface{})}
}
newKeys := make([]string, len(om.keys))
copy(newKeys, om.keys)
sort.Slice(newKeys, func(i, j int) bool {
pi, okI := jsonSchemaPriority[newKeys[i]]
pj, okJ := jsonSchemaPriority[newKeys[j]]
switch {
case okI && okJ:
return pi < pj
case okI:
return true
case okJ:
return false
default:
return newKeys[i] < newKeys[j]
}
})
newValues := make(map[string]interface{}, len(om.values))
for k, v := range om.values {
switch nested := v.(type) {
case *OrderedMap:
newValues[k] = nested.SortedCopy()
case map[string]interface{}:
newValues[k] = OrderedMapFromMap(nested).SortedCopy()
case []interface{}:
newValues[k] = sortedCopySlice(nested)
default:
newValues[k] = v
}
}
return &OrderedMap{keys: newKeys, values: newValues}
}
func sortedCopySlice(s []interface{}) []interface{} {
out := make([]interface{}, len(s))
for i, item := range s {
switch v := item.(type) {
case *OrderedMap:
out[i] = v.SortedCopy()
case map[string]interface{}:
out[i] = OrderedMapFromMap(v).SortedCopy()
case []interface{}:
out[i] = sortedCopySlice(v)
default:
out[i] = item
}
}
return out
}
// SortedCopyPreservingProperties is like SortedCopy but preserves the key
// order of user-defined property names inside "properties" maps. Structural
// JSON Schema keys (type, description, properties, required) are still sorted
// by priority, and all other keys alphabetically. When the key "properties"
// is encountered, its value (an OrderedMap of user-defined field names) has
// its top-level key order preserved while each nested schema value is
// recursively processed with the same property-aware logic.
//
// This ensures deterministic serialization for prompt caching (structural keys
// are always in the same order) while preserving the client's intended field
// generation order for LLM structured output.
func (om *OrderedMap) SortedCopyPreservingProperties() *OrderedMap {
if om == nil {
return nil
}
if len(om.keys) == 0 {
return &OrderedMap{values: make(map[string]interface{})}
}
newKeys := make([]string, len(om.keys))
copy(newKeys, om.keys)
sort.Slice(newKeys, func(i, j int) bool {
pi, okI := jsonSchemaPriority[newKeys[i]]
pj, okJ := jsonSchemaPriority[newKeys[j]]
switch {
case okI && okJ:
return pi < pj
case okI:
return true
case okJ:
return false
default:
return newKeys[i] < newKeys[j]
}
})
newValues := make(map[string]interface{}, len(om.values))
for k, v := range om.values {
if k == "properties" {
// User-defined property names: preserve key order, sort nested schemas
newValues[k] = preserveKeysOrderedCopyWithAwareness(v)
} else {
switch nested := v.(type) {
case *OrderedMap:
newValues[k] = nested.SortedCopyPreservingProperties()
case map[string]interface{}:
newValues[k] = OrderedMapFromMap(nested).SortedCopyPreservingProperties()
case []interface{}:
newValues[k] = sortedCopySlicePreservingProperties(nested)
default:
newValues[k] = v
}
}
}
return &OrderedMap{keys: newKeys, values: newValues}
}
// preserveKeysOrderedCopyWithAwareness copies an OrderedMap preserving its
// top-level key order (these are user-defined property names) while
// recursively applying SortedCopyPreservingProperties to each value (each
// value is a schema that may itself contain "properties").
// If the input is not an *OrderedMap, it falls back to SortedCopyPreservingProperties.
func preserveKeysOrderedCopyWithAwareness(v interface{}) interface{} {
switch om := v.(type) {
case *OrderedMap:
return om.preserveKeysWithPropertyAwareness()
case map[string]interface{}:
// Plain maps have non-deterministic iteration order in Go;
// convert and sort since we can't preserve an order that doesn't exist.
return OrderedMapFromMap(om).SortedCopyPreservingProperties()
default:
return v
}
}
// preserveKeysWithPropertyAwareness preserves the top-level key order of this
// OrderedMap while recursively applying SortedCopyPreservingProperties to each
// nested value.
func (om *OrderedMap) preserveKeysWithPropertyAwareness() *OrderedMap {
if om == nil {
return nil
}
if len(om.keys) == 0 {
return &OrderedMap{values: make(map[string]interface{})}
}
// Preserve original key order (no sorting)
newKeys := make([]string, len(om.keys))
copy(newKeys, om.keys)
newValues := make(map[string]interface{}, len(om.values))
for k, v := range om.values {
switch nested := v.(type) {
case *OrderedMap:
newValues[k] = nested.SortedCopyPreservingProperties()
case map[string]interface{}:
newValues[k] = OrderedMapFromMap(nested).SortedCopyPreservingProperties()
case []interface{}:
newValues[k] = sortedCopySlicePreservingProperties(nested)
default:
newValues[k] = v
}
}
return &OrderedMap{keys: newKeys, values: newValues}
}
func sortedCopySlicePreservingProperties(s []interface{}) []interface{} {
out := make([]interface{}, len(s))
for i, item := range s {
switch v := item.(type) {
case *OrderedMap:
out[i] = v.SortedCopyPreservingProperties()
case map[string]interface{}:
out[i] = OrderedMapFromMap(v).SortedCopyPreservingProperties()
case []interface{}:
out[i] = sortedCopySlicePreservingProperties(v)
default:
out[i] = item
}
}
return out
}
// decodeOrderedValue reads a single JSON value from the decoder.
// Objects are decoded as *OrderedMap (preserving key order).
// Arrays are decoded as []interface{} with each element recursively decoded.
// Primitives are returned as their Go equivalents.
func decodeOrderedValue(dec *json.Decoder) (interface{}, error) {
t, err := dec.Token()
if err != nil {
return nil, err
}
switch v := t.(type) {
case json.Delim:
if v == '{' {
// Recursively parse nested object as *OrderedMap
nested := NewOrderedMap()
for dec.More() {
keyToken, err := dec.Token()
if err != nil {
return nil, err
}
key, ok := keyToken.(string)
if !ok {
return nil, fmt.Errorf("expected string key, got %T", keyToken)
}
val, err := decodeOrderedValue(dec)
if err != nil {
return nil, err
}
nested.Set(key, val)
}
// Consume closing '}'
if _, err := dec.Token(); err != nil {
return nil, err
}
return nested, nil
}
if v == '[' {
// Parse array elements recursively
var arr []interface{}
for dec.More() {
val, err := decodeOrderedValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, val)
}
// Consume closing ']'
if _, err := dec.Token(); err != nil {
return nil, err
}
if arr == nil {
arr = []interface{}{}
}
return arr, nil
}
return nil, fmt.Errorf("unexpected delimiter: %v", v)
case string:
return v, nil
case float64:
return v, nil
case bool:
return v, nil
case nil:
return nil, nil
case json.Number:
f, err := v.Float64()
if err != nil {
return v.String(), nil
}
return f, nil
default:
return v, nil
}
}