feat: add json_Object and json_Encode template funcs

This commit is contained in:
2026-04-12 04:42:52 +04:00
parent 88f5171132
commit cea692cf1b
3 changed files with 94 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ Sarin supports Go templates in URL paths, methods, bodies, headers, params, cook
- [General Functions](#general-functions)
- [String Functions](#string-functions)
- [Collection Functions](#collection-functions)
- [JSON Functions](#json-functions)
- [Time Functions](#time-functions)
- [Crypto Functions](#crypto-functions)
- [Body Functions](#body-functions)
@@ -117,6 +118,33 @@ sarin -U http://example.com/users \
| `slice_Int(values ...int)` | Create int slice | `{{ slice_Int 1 2 3 }}` |
| `slice_Uint(values ...uint)` | Create uint slice | `{{ slice_Uint 1 2 3 }}` |
### JSON Functions
Build JSON payloads programmatically without manual quoting or escaping. `json_Object` is the ergonomic shortcut for flat objects; `json_Encode` marshals any value (slice, map, etc.) to a JSON string.
| Function | Description | Example |
| --------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
| `json_Object(pairs ...any)` | Build an object from interleaved key-value pairs and return it as a JSON string. Keys must be strings. | `{{ json_Object "name" "Alice" "age" 30 }}` |
| `json_Encode(v any)` | Marshal any value (slice, map, etc.) to a JSON string. | `{{ json_Encode (slice_Str "a" "b") }}``["a","b"]` |
**Examples:**
```yaml
# Flat object with fake data
body: '{{ json_Object "name" (fakeit_FirstName) "email" (fakeit_Email) }}'
# Embed a solved captcha token
body: '{{ json_Object "g-recaptcha-response" (twocaptcha_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com") }}'
# Encode a slice as a JSON array
body: '{{ json_Encode (slice_Str "a" "b" "c") }}'
# Encode a string dictionary (map[string]string)
body: '{{ json_Encode (dict_Str "key1" "value1" "key2" "value2") }}'
```
> **Note:** Object keys are serialized in alphabetical order (Go's `encoding/json` default), not insertion order. For API payloads this is almost always fine because JSON key order is semantically irrelevant.
### Time Functions
| Function | Description | Example |

View File

@@ -7,6 +7,7 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"math/rand/v2"
"mime/multipart"
"strings"
@@ -85,6 +86,38 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
"slice_Uint": func(values ...uint) []uint { return values },
"slice_Join": strings.Join,
// JSON
// json_Encode marshals any value to a JSON string.
// Usage: {{ json_Encode (dict_Str "key" "value") }}
"json_Encode": func(v any) (string, error) {
data, err := json.Marshal(v)
if err != nil {
return "", types.NewJSONEncodeError(err)
}
return string(data), nil
},
// json_Object builds a JSON object from interleaved key-value pairs and returns it
// as a JSON string. Keys must be strings; values may be any JSON-encodable type.
// Usage: {{ json_Object "name" "Alice" "age" 30 }}
"json_Object": func(pairs ...any) (string, error) {
if len(pairs)%2 != 0 {
return "", types.ErrJSONObjectOddArgs
}
obj := make(map[string]any, len(pairs)/2)
for i := 0; i < len(pairs); i += 2 {
key, ok := pairs[i].(string)
if !ok {
return "", types.NewJSONObjectKeyError(i, pairs[i])
}
obj[key] = pairs[i+1]
}
data, err := json.Marshal(obj)
if err != nil {
return "", types.NewJSONEncodeError(err)
}
return string(data), nil
},
// Time
"time_NowUnix": func() int64 { return time.Now().Unix() },
"time_NowUnixMilli": func() int64 { return time.Now().UnixMilli() },

View File

@@ -208,8 +208,41 @@ func (e URLParseError) Unwrap() error {
var (
ErrFileCacheNotInitialized = errors.New("file cache is not initialized")
ErrFormDataOddArgs = errors.New("body_FormData requires an even number of arguments (key-value pairs)")
ErrJSONObjectOddArgs = errors.New("json_Object requires an even number of arguments (key-value pairs)")
)
type JSONObjectKeyError struct {
Index int
Value any
}
func NewJSONObjectKeyError(index int, value any) JSONObjectKeyError {
return JSONObjectKeyError{Index: index, Value: value}
}
func (e JSONObjectKeyError) Error() string {
return fmt.Sprintf("json_Object key at index %d must be a string, got %T", e.Index, e.Value)
}
type JSONEncodeError struct {
Err error
}
func NewJSONEncodeError(err error) JSONEncodeError {
if err == nil {
err = errNoError
}
return JSONEncodeError{Err: err}
}
func (e JSONEncodeError) Error() string {
return "json_Encode failed: " + e.Err.Error()
}
func (e JSONEncodeError) Unwrap() error {
return e.Err
}
type TemplateParseError struct {
Err error
}