Special Types
Censor provides support for special Go types that require specific handling. This guide details how these types are processed.
Custom Types
Type Aliases
Censor handles type aliases by processing them according to their underlying type:
// String alias.
type Email string
// Struct with Email type.
type User struct {
ID string `censor:"display"`
Email Email
}
user := User{
ID: "123",
Email: "user@example.com",
}
// TEXT output: {ID:123 Email:[CENSORED]}.
// JSON output: {"ID":"123","Email":"[CENSORED]"}.
Custom Structs
Custom struct types are processed like regular structs:
// Custom struct type.
type Credentials struct {
Username string
Password string
}
// Type alias for Credentials.
type UserCredentials Credentials
// Usage.
creds := UserCredentials{
Username: "johndoe",
Password: "secret123",
}
// TEXT output: {Username:[CENSORED] Password:[CENSORED]}.
// JSON output: {"Username":"[CENSORED]","Password":"[CENSORED]"}.
Interfaces
Censor processes interfaces by examining their concrete type at runtime and applying the appropriate masking:
// Interface handling.
type Data interface{}
// Use with different types.
var stringData Data = "sensitive-data"
var intData Data = 42
var boolData Data = true
// String will be masked.
// TEXT output: [CENSORED].
// JSON output: "[CENSORED]".
// Int will be displayed.
// TEXT output: 42.
// JSON output: 42.
// Bool will be displayed.
// TEXT output: true.
// JSON output: true.
// Struct will have its fields masked according to the rules.
type User struct {
ID string `censor:"display"`
Email string
}
var userData Data = User{
ID: "123",
Email: "user@example.com",
}
// TEXT output: {ID:123 Email:[CENSORED]}.
// JSON output: {"ID":"123","Email":"[CENSORED]"}.
Any Type
The any
type (alias for interface{}
) is handled the same way as interfaces:
// Map with any values.
data := map[string]any{
"id": "123",
"email": "user@example.com",
"active": true,
"score": 95,
"metadata": map[string]string{
"lastLogin": "2023-01-01",
},
}
// TEXT output: map[id:123 email:[CENSORED] active:true score:95 metadata:map[lastLogin:[CENSORED]]].
// JSON output: {"id":"123","email":"[CENSORED]","active":true,"score":95,"metadata":{"lastLogin":"[CENSORED]"}}.
Custom Type Handlers
You can register custom handlers for specific types:
package main
import (
"fmt"
"time"
"github.com/vpakhuchyi/censor"
)
// Custom type.
type CreditCard struct {
Number string
ExpMonth int
ExpYear int
CVV string
}
func main() {
// Register custom type handler.
c := censor.New(
censor.WithTypeHandler(func(cc CreditCard) interface{} {
// Mask all except last 4 digits.
if len(cc.Number) > 4 {
return CreditCard{
Number: "****-****-****-" + cc.Number[len(cc.Number)-4:],
ExpMonth: cc.ExpMonth,
ExpYear: cc.ExpYear,
CVV: "***",
}
}
return CreditCard{
Number: "[CENSORED]",
ExpMonth: cc.ExpMonth,
ExpYear: cc.ExpYear,
CVV: "***",
}
}),
)
card := CreditCard{
Number: "4111-1111-1111-1111",
ExpMonth: 12,
ExpYear: 2025,
CVV: "123",
}
// Process the data.
masked := c.Process(card)
fmt.Printf("%+v\n", masked)
// TEXT output: {Number:****-****-****-1111 ExpMonth:12 ExpYear:2025 CVV:***}.
// JSON output: {"Number":"****-****-****-1111","ExpMonth":12,"ExpYear":2025,"CVV":"***"}.
}
Circular References
Censor can handle circular references in your data structures:
type Node struct {
ID string `censor:"display"`
Name string
Parent *Node
Children []*Node
}
// Create nodes with circular references.
parent := &Node{
ID: "1",
Name: "Parent",
Children: make([]*Node, 0),
}
child := &Node{
ID: "2",
Name: "Child",
Parent: parent,
}
parent.Children = append(parent.Children, child)
// Censor processes this correctly, preventing infinite recursion.
// TEXT output: {ID:1 Name:[CENSORED] Parent:<nil> Children:[{ID:2 Name:[CENSORED] Parent:0xc00010e100 Children:[]}]}.
// JSON output: {"ID":"1","Name":"[CENSORED]","Parent":null,"Children":[{"ID":"2","Name":"[CENSORED]","Parent":{"ID":"1","Name":"[CENSORED]","Parent":null,"Children":null},"Children":[]}]}.
Reflection Edge Cases
Censor handles various reflection edge cases:
Zero Values
// Zero values.
var s string // "".
var i int // 0.
var b bool // false.
var f float64 // 0.0.
var t time.Time // Zero time.
// String zero value will not be masked.
// TEXT output: .
// JSON output: "".
// Other zero values are displayed as is.
// TEXT output for i: 0.
// TEXT output for b: false.
// TEXT output for f: 0.
// TEXT output for t: 0001-01-01T00:00:00Z.
Unexported Fields
Censor respects Go's visibility rules and doesn't access unexported fields:
type User struct {
ID string `censor:"display"`
Email string
password string // unexported.
}
user := User{
ID: "123",
Email: "user@example.com",
password: "secret123",
}
// The unexported password field is not included in the output.
// TEXT output: {ID:123 Email:[CENSORED]}
// JSON output: {"ID":"123","Email":"[CENSORED]"}
Complete Example
Here's a complete example showing how Censor handles various special types:
package main
import (
"fmt"
"github.com/vpakhuchyi/censor"
)
// Type alias
type Email string
// Custom type
type CreditCard struct {
Number string
ExpMonth int
ExpYear int
CVV string
}
// User with various special types
type User struct {
ID string `censor:"display"`
Email Email
Card CreditCard
Data interface{}
AnyData any
Temporary interface{} // Will be nil
}
func main() {
// Create a Censor instance with custom type handler
c := censor.New(
censor.WithTypeHandler(func(cc CreditCard) interface{} {
// Mask all except last 4 digits
if len(cc.Number) > 4 {
return CreditCard{
Number: "****-****-****-" + cc.Number[len(cc.Number)-4:],
ExpMonth: cc.ExpMonth,
ExpYear: cc.ExpYear,
CVV: "***",
}
}
return CreditCard{
Number: "[CENSORED]",
ExpMonth: cc.ExpMonth,
ExpYear: cc.ExpYear,
CVV: "***",
}
}),
)
// Create a user
user := User{
ID: "123",
Email: "user@example.com",
Card: CreditCard{
Number: "4111-1111-1111-1111",
ExpMonth: 12,
ExpYear: 2025,
CVV: "123",
},
Data: map[string]string{
"api_key": "sk_live_123456789",
},
AnyData: "sensitive-data",
}
// Process the data
masked := c.Process(user)
fmt.Printf("%+v\n", masked)
// TEXT output: {ID:123 Email:[CENSORED] Card:{Number:****-****-****-1111 ExpMonth:12 ExpYear:2025 CVV:***} Data:map[api_key:[CENSORED]] AnyData:[CENSORED] Temporary:<nil>}
// JSON output: {"ID":"123","Email":"[CENSORED]","Card":{"Number":"****-****-****-1111","ExpMonth":12,"ExpYear":2025,"CVV":"***"},"Data":{"api_key":"[CENSORED]"},"AnyData":"[CENSORED]","Temporary":null}
}
Next Steps
- Learn about Basic Types
- Check out Complex Types
- See Format-Specific handling