diff --git a/docs/index.md b/docs/index.md index 3999f8d..87e54a8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -180,7 +180,7 @@ Table below shows the names of the configuration options: |----------------------|------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| | OutputFormat | output-format | text | The output format that will be used for the formatted values (text or json). | | PrintConfigOnInit | print-config-on-init | false | If true, the configuration will be printed when any of available constructors is used. | -| UseJSONTagName | use-json-tag-name | false | If true, the JSON tag name will be used instead of the Go struct field name. | +| UseJSONTagName | use-json-tag-name | false | If true, struct fields encoded as JSON reuse their `json` tag name. Fields tagged with `json:"-"` stay hidden, and tags without a name fall back to the Go identifier. | | MaskValue | mask-value | [CENSORED] | The value that will be used to mask the sensitive information. | | DisplayStructName | display-struct-name | false | If true, the struct name will be displayed in the output. | | DisplayMapType | display-map-type | false | If true, the map type will be displayed in the output. | @@ -189,6 +189,8 @@ Table below shows the names of the configuration options: | ExcludePatterns | exclude-patterns | [] | A list of regular expressions that will be compared against all the string values.
If a value matches any of the patterns, that section will be masked. Up to 50 patterns are allowed. | +Enabling `UseJSONTagName` mirrors Go's `encoding/json`: only the portion before the first comma is used for the field name, `json:"-"` keeps the field hidden, and tags without an explicit name fall back to the original identifier. + ### Using the `censor.Config` struct It's possible to define a configuration using `censor.Config` struct: diff --git a/internal/encoder/json_encoder.go b/internal/encoder/json_encoder.go index 93fed80..608326e 100644 --- a/internal/encoder/json_encoder.go +++ b/internal/encoder/json_encoder.go @@ -6,6 +6,7 @@ import ( "encoding/json" "reflect" "strconv" + "strings" "unicode/utf8" "github.com/shopspring/decimal" @@ -20,6 +21,7 @@ func NewJSONEncoder(c Config) *JSONEncoder { CensorFieldTag: defaultCensorFieldTag, ExcludePatterns: c.ExcludePatterns, MaskValue: c.MaskValue, + UseJSONTagName: c.UseJSONTagName, structFieldsCache: cache.NewTypeCache[[]Field](cache.DefaultMaxCacheSize), escapedStringsCache: cache.New[string](cache.DefaultMaxCacheSize), regexpCache: cache.New[string](cache.DefaultMaxCacheSize), @@ -133,6 +135,7 @@ func (e *JSONEncoder) Struct(b *bytes.Buffer, v reflect.Value) { b.WriteByte('}') } +//nolint:gocognit func (e *JSONEncoder) getStructFields(v reflect.Value) []Field { numFields := v.NumField() fields := make([]Field, numFields) @@ -143,8 +146,27 @@ func (e *JSONEncoder) getStructFields(v reflect.Value) []Field { continue } + name := field.Name + + if e.UseJSONTagName { + tagValue := field.Tag.Get("json") + if tagValue != "" { + if commaIdx := strings.Index(tagValue, ","); commaIdx != -1 { + tagValue = tagValue[:commaIdx] + } + + if tagValue == "-" { + continue + } + + if tagValue != "" { + name = tagValue + } + } + } + fields[i] = Field{ - Name: field.Name, + Name: name, IsMasked: field.Tag.Get(e.CensorFieldTag) != display, } } diff --git a/internal/encoder/json_encoder_test.go b/internal/encoder/json_encoder_test.go index e7470e8..2d67b47 100644 --- a/internal/encoder/json_encoder_test.go +++ b/internal/encoder/json_encoder_test.go @@ -724,3 +724,39 @@ func TestJSONEncoder_StringEscaped(t *testing.T) { require.Equal(t, `"say \"email: [CENSORED]\""`, b.String()) }) } + +func TestJSONEncoder_Struct_UseJSONTagName(t *testing.T) { + type payload struct { + Tagged string `json:"alias,omitempty" censor:"display"` + Skipped string `json:"-" censor:"display"` + Empty string `json:",omitempty" censor:"display"` + Default string `censor:"display"` + } + + value := payload{ + Tagged: "tagged", + Skipped: "skipped", + Empty: "empty", + Default: "default", + } + + t.Run("disabled uses Go field names", func(t *testing.T) { + e := NewJSONEncoder(Config{}) + var b bytes.Buffer + defer b.Reset() + + e.Struct(&b, reflect.ValueOf(value)) + + require.Equal(t, `{"Tagged": "tagged","Skipped": "skipped","Empty": "empty","Default": "default"}`, b.String()) + }) + + t.Run("enabled uses json tags and skips dash tag", func(t *testing.T) { + e := NewJSONEncoder(Config{UseJSONTagName: true}) + var b bytes.Buffer + defer b.Reset() + + e.Struct(&b, reflect.ValueOf(value)) + + require.Equal(t, `{"alias": "tagged","Empty": "empty","Default": "default"}`, b.String()) + }) +}