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 } }