diff --git a/.github/templates/README.template.md b/.github/templates/README.template.md
index d491da9..ef74679 100644
--- a/.github/templates/README.template.md
+++ b/.github/templates/README.template.md
@@ -31,10 +31,12 @@ endpoint restrictions, placeholders, flexible configuration
- [Getting Started](#getting-started)
- [Setup](#setup)
- [Usage](#usage)
-- [Best Practices](#security-best-practices)
+- [Best Practices](#best-practices)
- [Configuration](#configuration)
- [Endpoints](#endpoints)
- [Variables](#variables)
+ - [Data Aliases](#data-aliases)
+ - [Message Templates](#message-templates)
- [Contributing](#contributing)
- [Support](#support)
- [License](#license)
@@ -119,31 +121,24 @@ curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer API_T
#### Placeholders
-If you are not comfortable / don't want to hardcode your Number for example and/or Recipients in you, may use **Placeholders** in your Request. See [Custom Variables](#variables).
+If you are not comfortable / don't want to hardcode your Number for example and/or Recipients in you, may use **Placeholders** in your Request.
-These Placeholders can be used in the Request Query or the Body of a Request like so:
+You can use [**Variable**](#variables) `{{.NUMBER}}` Placeholders and **Body** Placeholders `{{@data.key}}`.
-**Body**
+| Type | Example |
+| :---- | :--------------------------------------------------------------- |
+| Body | `{"number": "{{ .NUMBER }}", "recipients": "{{ .RECIPIENTS }}"}` |
+| Query | `http://sec-signal-api:8880/v1/receive/?@number={{.NUMBER}}` |
+| Path | `http://sec-signal-api:8880/v1/receive/{{.NUMBER}}` |
+
+You can also combine them:
```json
{
- "number": "{{ .NUMBER }}",
- "recipients": "{{ .RECIPIENTS }}"
+ "content": "{{.NUMBER}} -> {{.RECIPIENTS}}"
}
```
-**Query**
-
-```
-http://sec-signal-api:8880/v1/receive/?@number={{.NUMBER}}
-```
-
-**Path**
-
-```
-http://sec-signal-api:8880/v1/receive/{{.NUMBER}}
-```
-
#### KeyValue Pair Injection
In some cases you may not be able to access / modify the Request Body, in that case specify needed values in the Request Query:
@@ -153,7 +148,7 @@ In some cases you may not be able to access / modify the Request Body, in that c
In order to differentiate Injection Queries and _regular_ Queries
you have to add `@` in front of any KeyValue Pair assignment.
-Supported types include **strings**, **ints** and **arrays**. See [Formatting](#string-to-type).
+Supported types include **strings**, **ints**, **arrays** and **json dictionaries**. See [Formatting](#string-to-type).
## Best Practices
@@ -293,12 +288,27 @@ settings:
recipients: ["+123400002", "group.id", "user.id"]
```
-### Message Aliases
+### Message Templates
-To improve compatibility with other services Secured Signal API provides **Message Aliases** for the `message` attribute.
+To customize the `message` attribute you can use **Message Templates** to build your message by using other Body Keys and Variables.
+Use `messageTemplate` to configure:
+
+```yaml
+settings:
+ messageTemplate: |
+ Your Message:
+ {{@message}}.
+ Sent with Secured Signal API.
+```
+
+Use `{{@data.key}}` to reference Body Keys and `{{.KEY}}` for Variables.
+
+### Data Aliases
+
+To improve compatibility with other services Secured Signal API provides **Data Aliases** and a built-in `message` Alias.
-Default Message Aliases
+Default `message` Aliases
| Alias | Score | Alias | Score |
| ------------ | ----- | ---------------- | ----- |
@@ -312,23 +322,27 @@ To improve compatibility with other services Secured Signal API provides **Messa
-Secured Signal API will pick the best scoring Message Alias (if available) to extract the correct message from the Request Body.
+Secured Signal API will pick the best scoring Data Alias (if available) to extract set the Key to the correct Value from the Request Body.
-Message Aliases can be added by setting `messageAliases` in your config:
+Data Aliases can be added by setting `dataAliases` in your config:
```yaml
settings:
- messageAliases:
- [
- { alias: "msg", score: 80 },
- { alias: "data.message", score: 79 },
- { alias: "array[0].message", score: 78 },
- ]
+ dataAliases:
+ "@message":
+ [
+ { alias: "msg", score: 80 },
+ { alias: "data.message", score: 79 },
+ { alias: "array[0].message", score: 78 },
+ ]
+ ".NUMBER": [{ alias: "phone_number", score: 100 }]
```
+Use `@` for aliasing Body Keys and `.` for aliasing Variables.
+
### Port
-To change the Port which Secured Signal API uses, you need to set `server.port` in your config. (default: `8880`)
+To change the Port which Secured Signal API uses, you need to set `service.port` in your config. (default: `8880`)
### Log Level
diff --git a/README.md b/README.md
index 5575000..5337f8c 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ endpoint restrictions, placeholders, flexible configuration
- [Configuration](#configuration)
- [Endpoints](#endpoints)
- [Variables](#variables)
+ - [Message Templates](#message-templates)
- [Contributing](#contributing)
- [Support](#support)
- [License](#license)
@@ -65,10 +66,9 @@ services:
container_name: secured-signal
environment:
API__URL: http://signal-api:8080
- SETTINGS__VARIABLES__RECIPIENTS:
- '[+123400002, +123400003, +123400004]'
+ SETTINGS__VARIABLES__RECIPIENTS: "[+123400002, +123400003, +123400004]"
SETTINGS__VARIABLES__NUMBER: "+123400001"
- API__TOKENS: '[LOOOOOONG_STRING]'
+ API__TOKENS: "[LOOOOOONG_STRING]"
ports:
- "8880:8880"
restart: unless-stopped
@@ -100,10 +100,9 @@ services:
container_name: secured-signal
environment:
API__URL: http://signal-api:8080
- SETTINGS__VARIABLES__RECIPIENTS:
- '[+123400002,+123400003,+123400004]'
+ SETTINGS__VARIABLES__RECIPIENTS: "[+123400002,+123400003,+123400004]"
SETTINGS__VARIABLES__NUMBER: "+123400001"
- API__TOKENS: '[LOOOOOONG_STRING]'
+ API__TOKENS: "[LOOOOOONG_STRING]"
labels:
- traefik.enable=true
- traefik.http.routers.signal-api.rule=Host(`signal-api.mydomain.com`)
@@ -438,39 +437,6 @@ settings:
recipients: ["+123400002", "group.id", "user.id"]
```
-### Message Aliases
-
-To improve compatibility with other services Secured Signal API provides **Message Aliases** for the `message` attribute.
-
-
-Default Message Aliases
-
-| Alias | Score | Alias | Score |
-| ------------ | ----- | ---------------- | ----- |
-| msg | 100 | data.content | 9 |
-| content | 99 | data.description | 8 |
-| description | 98 | data.text | 7 |
-| text | 20 | data.summary | 6 |
-| summary | 15 | data.details | 5 |
-| details | 14 | body | 2 |
-| data.message | 10 | data | 1 |
-
-
-
-Secured Signal API will pick the best scoring Message Alias (if available) to extract the correct message from the Request Body.
-
-Message Aliases can be added by setting `messageAliases` in your config:
-
-```yaml
-settings:
- messageAliases:
- [
- { alias: "msg", score: 80 },
- { alias: "data.message", score: 79 },
- { alias: "array[0].message", score: 78 },
- ]
-```
-
### Port
To change the Port which Secured Signal API uses, you need to set `server.port` in your config. (default: `8880`)
diff --git a/data/defaults.yml b/data/defaults.yml
index 82ec91c..229832b 100644
--- a/data/defaults.yml
+++ b/data/defaults.yml
@@ -4,25 +4,26 @@ service:
logLevel: info
settings:
- messageAliases:
- [
- { alias: msg, score: 100 },
- { alias: content, score: 99 },
- { alias: description, score: 98 },
- { alias: text, score: 20 },
- { alias: summary, score: 15 },
- { alias: details, score: 14 },
+ dataAliases:
+ "@message":
+ [
+ { alias: msg, score: 100 },
+ { alias: content, score: 99 },
+ { alias: description, score: 98 },
+ { alias: text, score: 20 },
+ { alias: summary, score: 15 },
+ { alias: details, score: 14 },
- { alias: data.message, score: 10 },
- { alias: data.content, score: 9 },
- { alias: data.description, score: 8 },
- { alias: data.text, score: 7 },
- { alias: data.summary, score: 6 },
- { alias: data.details, score: 5 },
+ { alias: data.message, score: 10 },
+ { alias: data.content, score: 9 },
+ { alias: data.description, score: 8 },
+ { alias: data.text, score: 7 },
+ { alias: data.summary, score: 6 },
+ { alias: data.details, score: 5 },
- { alias: body, score: 2 },
- { alias: data, score: 1 },
- ]
+ { alias: body, score: 2 },
+ { alias: data, score: 1 },
+ ]
variables:
recipients: ${RECIPIENTS}
diff --git a/examples/config.yml b/examples/config.yml
index 9be5a31..2005892 100644
--- a/examples/config.yml
+++ b/examples/config.yml
@@ -1,18 +1,26 @@
# Example Config (all configurations shown)
+service:
+ port: 8880
api:
- port: 8880
url: http://signal-api:8080
tokens: [token1, token2]
-logLevel: INFO
+logLevel: info
settings:
+ messageTemplate: |
+ You've got a Notification:
+ {{@message}}
+ At {{@data.timestamp}} on {{@data.date}}.
+ Send using {{.NUMBER}}.
+
variables:
number: "+123400001"
recipients: ["+123400002", "group.id", "user.id"]
- messageAliases: [{ alias: "msg", score: 100 }]
+ dataAliases:
+ "@message": [{ alias: "msg", score: 100 }]
blockedEndpoints:
- /v1/about
diff --git a/internals/proxy/middlewares/body.go b/internals/proxy/middlewares/aliases.go
similarity index 52%
rename from internals/proxy/middlewares/body.go
rename to internals/proxy/middlewares/aliases.go
index 25f01fe..d38e81a 100644
--- a/internals/proxy/middlewares/body.go
+++ b/internals/proxy/middlewares/aliases.go
@@ -12,18 +12,24 @@ import (
request "github.com/codeshelldev/secured-signal-api/utils/request"
)
-type BodyMiddleware struct {
+type AliasMiddleware struct {
Next http.Handler
}
-func (data BodyMiddleware) Use() http.Handler {
+func (data AliasMiddleware) Use() http.Handler {
next := data.Next
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- messageAliases := getSettingsByReq(req).MESSAGE_ALIASES
+ settings := getSettingsByReq(req)
- if messageAliases == nil {
- messageAliases = getSettings("*").MESSAGE_ALIASES
+ dataAliases := settings.DATA_ALIASES
+
+ if dataAliases == nil {
+ dataAliases = getSettings("*").DATA_ALIASES
+ }
+
+ if settings.VARIABLES == nil {
+ settings.VARIABLES = getSettings("*").VARIABLES
}
body, err := request.GetReqBody(w, req)
@@ -38,13 +44,20 @@ func (data BodyMiddleware) Use() http.Handler {
if !body.Empty {
bodyData = body.Data
- content, ok := bodyData["message"]
+ aliasData := processDataAliases(dataAliases, bodyData)
- if !ok || content == "" {
+ for key, value := range aliasData {
+ prefix := key[:1]
- bodyData["message"], bodyData = getMessage(messageAliases, bodyData)
+ keyWithoutPrefix := key[1:]
- modifiedBody = true
+ switch prefix {
+ case "@":
+ bodyData[keyWithoutPrefix] = value
+ modifiedBody = true
+ case ".":
+ settings.VARIABLES[keyWithoutPrefix] = value
+ }
}
}
@@ -70,32 +83,44 @@ func (data BodyMiddleware) Use() http.Handler {
})
}
-func getMessage(aliases []middlewareTypes.MessageAlias, data map[string]any) (string, map[string]any) {
- var content string
+func processDataAliases(aliases map[string][]middlewareTypes.DataAlias, data map[string]any) (map[string]any) {
+ aliasData := map[string]any{}
+
+ for key, alias := range aliases {
+ key, value := getData(key, alias, data)
+
+ aliasData[key] = value
+ }
+
+ return aliasData
+}
+
+func getData(key string, aliases []middlewareTypes.DataAlias, data map[string]any) (string, any) {
var best int
+ var value any
for _, alias := range aliases {
aliasValue, score, ok := processAlias(alias, data)
- if ok && score > best {
- content = aliasValue
- }
+ if ok {
+ if score > best {
+ value = aliasValue
+ }
- data[alias.Alias] = nil
+ data[alias.Alias] = nil
+ }
}
- return content, data
+ return key, value
}
-func processAlias(alias middlewareTypes.MessageAlias, data map[string]any) (string, int, bool) {
+func processAlias(alias middlewareTypes.DataAlias, data map[string]any) (any, int, bool) {
aliasKey := alias.Alias
value, ok := jsonutils.GetByPath(aliasKey, data)
- aliasValue, isStr := value.(string)
-
- if isStr && ok && aliasValue != "" {
- return aliasValue, alias.Score, true
+ if ok && value != nil {
+ return value, alias.Score, true
} else {
return "", 0, false
}
diff --git a/internals/proxy/middlewares/message.go b/internals/proxy/middlewares/message.go
new file mode 100644
index 0000000..5761a7a
--- /dev/null
+++ b/internals/proxy/middlewares/message.go
@@ -0,0 +1,94 @@
+package middlewares
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+ "strconv"
+
+ log "github.com/codeshelldev/secured-signal-api/utils/logger"
+ request "github.com/codeshelldev/secured-signal-api/utils/request"
+)
+
+type MessageMiddleware struct {
+ Next http.Handler
+}
+
+func (data MessageMiddleware) Use() http.Handler {
+ next := data.Next
+
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ settings := getSettingsByReq(req)
+
+ variables := settings.VARIABLES
+ messageTemplate := settings.MESSAGE_TEMPLATE
+
+ if variables == nil {
+ variables = getSettings("*").VARIABLES
+ }
+
+ if messageTemplate == "" {
+ messageTemplate = getSettings("*").MESSAGE_TEMPLATE
+ }
+
+
+ body, err := request.GetReqBody(w, req)
+
+ if err != nil {
+ log.Error("Could not get Request Body: ", err.Error())
+ }
+
+ bodyData := map[string]any{}
+
+ var modifiedBody bool
+
+ if !body.Empty {
+ bodyData = body.Data
+
+ newData, err := TemplateMessage(messageTemplate, bodyData, variables)
+
+ if err != nil {
+ log.Error("Error Templating Message: ", err.Error())
+ }
+
+ if newData["message"] != bodyData["message"] && newData["message"] != "" {
+ bodyData = newData
+ modifiedBody = true
+ }
+ }
+
+ if modifiedBody {
+ modifiedBody, err := request.CreateBody(bodyData)
+
+ if err != nil {
+ http.Error(w, "Internal Error", http.StatusInternalServerError)
+ return
+ }
+
+ body = modifiedBody
+
+ strData := body.ToString()
+
+ log.Debug("Applied Message Templating: ", strData)
+
+ req.ContentLength = int64(len(strData))
+ req.Header.Set("Content-Length", strconv.Itoa(len(strData)))
+ }
+
+ req.Body = io.NopCloser(bytes.NewReader(body.Raw))
+
+ next.ServeHTTP(w, req)
+ })
+}
+
+func TemplateMessage(template string, data map[string]any, VARIABLES map[string]any) (map[string]any, error) {
+ data["message"] = template
+
+ data, ok, err := TemplateBody(data, VARIABLES)
+
+ if err != nil || !ok || data == nil {
+ return data, err
+ }
+
+ return data, nil
+}
\ No newline at end of file
diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go
index 25946ba..4365185 100644
--- a/internals/proxy/middlewares/template.go
+++ b/internals/proxy/middlewares/template.go
@@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/url"
+ "regexp"
"strconv"
jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils"
@@ -104,10 +105,28 @@ func (data TemplateMiddleware) Use() http.Handler {
})
}
-func TemplateBody(data map[string]any, VARIABLES any) (map[string]any, bool, error) {
+func TemplateBody(data map[string]any, VARIABLES map[string]any) (map[string]any, bool, error) {
var modified bool
- templatedData, err := templating.RenderJSONTemplate("body", data, VARIABLES)
+ jsonStr := jsonutils.ToJson(data)
+
+ if jsonStr != "" {
+ re, err := regexp.Compile(`{{\s*\@([a-zA-Z0-9_.]+)\s*}}`)
+
+ if err != nil {
+ return data, false, err
+ }
+
+ jsonStr = re.ReplaceAllString(jsonStr, "{{.$1}}")
+
+ normalizedData, err := jsonutils.GetJsonSafe[map[string]any](jsonStr)
+
+ if err == nil {
+ data = normalizedData
+ }
+ }
+
+ templatedData, err := templating.RenderJSON("body", data, VARIABLES)
if err != nil {
return data, false, err
diff --git a/internals/proxy/middlewares/types/types.go b/internals/proxy/middlewares/types/types.go
index ed96549..359f49b 100644
--- a/internals/proxy/middlewares/types/types.go
+++ b/internals/proxy/middlewares/types/types.go
@@ -3,7 +3,7 @@
package middlewareTypes
-type MessageAlias struct {
+type DataAlias struct {
Alias string `koanf:"alias"`
Score int `koanf:"score"`
}
\ No newline at end of file
diff --git a/main.go b/main.go
index 4cf1eff..bd1fe44 100644
--- a/main.go
+++ b/main.go
@@ -33,12 +33,16 @@ func main() {
proxy_last = proxy.Create(ENV.API_URL)
- body_m5 := middlewares.BodyMiddleware{
+ mesg_m6 := middlewares.MessageMiddleware{
Next: proxy_last,
}
+ alias_m5 := middlewares.AliasMiddleware{
+ Next: mesg_m6.Use(),
+ }
+
temp_m4 := middlewares.TemplateMiddleware{
- Next: body_m5.Use(),
+ Next: alias_m5.Use(),
}
endp_m3 := middlewares.EndpointsMiddleware{
@@ -79,4 +83,6 @@ func main() {
<-stop
docker.Shutdown(server)
-}
\ No newline at end of file
+}
+
+// TESTING
\ No newline at end of file
diff --git a/tests/json_test.go b/tests/json_test.go
index 399ba48..87f10bb 100644
--- a/tests/json_test.go
+++ b/tests/json_test.go
@@ -19,6 +19,7 @@ func TestJsonTemplating(t *testing.T) {
json := `
{
+ "multiple": "{{.key}}, {{.int}}",
"dict": { "key": "{{.key}}" },
"dictArray": [
{ "key": "{{.key}}" },
@@ -31,6 +32,7 @@ func TestJsonTemplating(t *testing.T) {
data := jsonutils.GetJson[map[string]any](json)
expected := map[string]any{
+ "multiple": "val, 4",
"dict": map[string]any{
"key": "val",
},
diff --git a/utils/config/loader.go b/utils/config/loader.go
index ea0b746..a226a6c 100644
--- a/utils/config/loader.go
+++ b/utils/config/loader.go
@@ -28,10 +28,11 @@ type ENV_ struct {
}
type SETTING_ struct {
- BLOCKED_ENDPOINTS []string `koanf:"blockedendpoints"`
- ALLOWED_ENDPOINTS []string `koanf:"allowedendpoints"`
- VARIABLES map[string]any `koanf:"variables"`
- MESSAGE_ALIASES []middlewareTypes.MessageAlias `koanf:"messagealiases"`
+ BLOCKED_ENDPOINTS []string `koanf:"blockedendpoints"`
+ ALLOWED_ENDPOINTS []string `koanf:"allowedendpoints"`
+ VARIABLES map[string]any `koanf:"variables"`
+ DATA_ALIASES map[string][]middlewareTypes.DataAlias `koanf:"dataaliases"`
+ MESSAGE_TEMPLATE string `koanf:"messagetemplate"`
}
var ENV *ENV_ = &ENV_{
@@ -79,14 +80,10 @@ func InitEnv() {
var settings SETTING_
- transformChildren(config, "settings.variables", func(key string, value any) (string, any) {
- return strings.ToUpper(key), value
- })
+ transformChildren(config, "settings.variables", transformVariables)
config.Unmarshal("settings", &settings)
- log.Dev(jsonutils.ToJson(settings))
-
ENV.SETTINGS["*"] = &settings
}
@@ -109,3 +106,7 @@ func LoadConfig() {
}
}
}
+
+func transformVariables(key string, value any) (string, any) {
+ return strings.ToUpper(key), value
+}
diff --git a/utils/config/tokens.go b/utils/config/tokens.go
index ce995a0..b271579 100644
--- a/utils/config/tokens.go
+++ b/utils/config/tokens.go
@@ -2,7 +2,6 @@ package config
import (
"strconv"
- "strings"
log "github.com/codeshelldev/secured-signal-api/utils/logger"
"github.com/knadh/koanf/parsers/yaml"
@@ -28,9 +27,7 @@ func InitTokens() {
var tokenConfigs []TOKEN_CONFIG_
- transformChildrenUnderArray(tokensLayer, "tokenconfigs", "overrides.variables", func(key string, value any) (string, any) {
- return strings.ToUpper(key), value
- })
+ transformChildrenUnderArray(tokensLayer, "tokenconfigs", "overrides.variables", transformVariables)
tokensLayer.Unmarshal("tokenconfigs", &tokenConfigs)
diff --git a/utils/templating/templating.go b/utils/templating/templating.go
index 37d4ec3..3f66c90 100644
--- a/utils/templating/templating.go
+++ b/utils/templating/templating.go
@@ -13,7 +13,6 @@ func normalize(value any) string {
switch str := value.(type) {
case []string:
return "[" + strings.Join(str, ",") + "]"
-
case []any:
items := make([]string, len(str))
@@ -36,6 +35,10 @@ func normalizeJSON(value any) string {
case []any, []string, map[string]any, int, float64, bool:
object, _ := json.Marshal(value)
+ if string(object) == "{}" {
+ return value.(string)
+ }
+
return "<<" + string(object) + ">>"
default:
@@ -43,6 +46,37 @@ func normalizeJSON(value any) string {
}
}
+func cleanQuotedPairsJSON(s string) string {
+ quoteRe, err := regexp.Compile(`"([^"]*?)"`)
+
+ if err != nil {
+ return s
+ }
+
+ pairRe, err := regexp.Compile(`<<([^<>]+)>>`)
+
+ if err != nil {
+ return s
+ }
+
+ return quoteRe.ReplaceAllStringFunc(s, func(container string) string {
+ inner := container[1 : len(container)-1] // remove quotes
+
+ matches := pairRe.FindAllStringSubmatchIndex(inner, -1)
+
+ // ONE pair which fills whole ""
+ if len(matches) == 1 && matches[0][0] == 0 && matches[0][1] == len(inner) {
+ return container // keep <<...>> untouched
+ }
+
+ // MULTIPLE pairs || that do not fill whole ""
+ inner = pairRe.ReplaceAllString(inner, "$1")
+ inner = strings.ReplaceAll(inner, `"`, `'`)
+
+ return `"` + inner + `"`
+ })
+}
+
func ParseTemplate(templt *template.Template, tmplStr string, variables any) (string, error) {
tmpl, err := templt.Parse(tmplStr)
@@ -69,7 +103,23 @@ func CreateTemplateWithFunc(name string, funcMap template.FuncMap) (*template.Te
return template.New(name).Funcs(funcMap)
}
-func RenderJSONTemplate(name string, data map[string]any, variables any) (map[string]any, error) {
+func RenderJSON(name string, data map[string]any, variables map[string]any) (map[string]any, error) {
+ combinedData := data
+
+ for key, value := range variables {
+ combinedData[key] = value
+ }
+
+ data, err := RenderJSONTemplate(name, data, combinedData)
+
+ if err != nil {
+ return data, err
+ }
+
+ return data, nil
+}
+
+func RenderJSONTemplate(name string, data map[string]any, variables map[string]any) (map[string]any, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
@@ -78,7 +128,7 @@ func RenderJSONTemplate(name string, data map[string]any, variables any) (map[st
tmplStr := string(jsonBytes)
- re, err := regexp.Compile(`{{\s*\.(\w+)\s*}}`)
+ re, err := regexp.Compile(`{{\s*\.([a-zA-Z0-9_.]+)\s*}}`)
// Add normalize() to be able to remove Quotes from Arrays
if err == nil {
@@ -95,6 +145,8 @@ func RenderJSONTemplate(name string, data map[string]any, variables any) (map[st
return nil, err
}
+ jsonStr = cleanQuotedPairsJSON(jsonStr)
+
// Remove the Quotes around "<<[item1,item2]>>"
re, err = regexp.Compile(`"<<(.*?)>>"`)