Skip to main content

Complex Types

Censor provides robust support for complex Go types. This guide details how each complex type is handled by Censor.

Structs

Structs are processed recursively, with each field being processed according to its type.

type Address struct {
Street string
City string
ZipCode string
}

type User struct {
ID string `censor:"display"`
Email string
Address Address
}

user := User{
ID: "123",
Email: "user@example.com",
Address: Address{
Street: "123 Main St",
City: "New York",
ZipCode: "10001",
},
}

// TEXT output: {ID:123 Email:[CENSORED] Address:{Street:[CENSORED] City:[CENSORED] ZipCode:[CENSORED]}}.
// JSON output: {"ID":"123","Email":"[CENSORED]","Address":{"Street":"[CENSORED]","City":"[CENSORED]","ZipCode":"[CENSORED]"}}.

Nested Structs

Censor processes nested structs to any depth:

type Contact struct {
Email string
Phone string
}

type Address struct {
Street string
City string
ZipCode string
}

type User struct {
ID string `censor:"display"`
Contact Contact
Address Address
}

user := User{
ID: "123",
Contact: Contact{
Email: "user@example.com",
Phone: "555-1234",
},
Address: Address{
Street: "123 Main St",
City: "New York",
ZipCode: "10001",
},
}

// TEXT output: {ID:123 Contact:{Email:[CENSORED] Phone:[CENSORED]} Address:{Street:[CENSORED] City:[CENSORED] ZipCode:[CENSORED]}}.
// JSON output: {"ID":"123","Contact":{"Email":"[CENSORED]","Phone":"[CENSORED]"},"Address":{"Street":"[CENSORED]","City":"[CENSORED]","ZipCode":"[CENSORED]"}}.

Embedded Structs

Censor handles embedded structs, processing all fields:

type Person struct {
Name string
Email string
}

type Employee struct {
Person
ID string `censor:"display"`
Title string
}

employee := Employee{
Person: Person{
Name: "John Doe",
Email: "john@example.com",
},
ID: "emp-123",
Title: "Software Engineer",
}

// TEXT output: {Person:{Name:[CENSORED] Email:[CENSORED]} ID:emp-123 Title:[CENSORED]}.
// JSON output: {"Name":"[CENSORED]","Email":"[CENSORED]","ID":"emp-123","Title":"[CENSORED]"}.

Maps

Maps are processed recursively, with both keys and values being processed according to their types.

Basic Maps

user := map[string]string{
"id": "123",
"email": "user@example.com",
"password": "secret123",
}

// TEXT output: map[id:123 email:[CENSORED] password:[CENSORED]].
// JSON output: {"id":"123","email":"[CENSORED]","password":"[CENSORED]"}.

You can configure the map key handling:

// Create a Censor instance with custom key handling.
// Return true to display the key, false to mask it.
// Processing with custom map key handler.
c := censor.New(
censor.WithMapKeyHandler(func(key string) bool {
// Return true to display the key, false to mask it.
return key == "id"
}),
)

user := map[string]string{
"id": "123",
"email": "user@example.com",
"password": "secret123",
}

// Processing with custom map key handler.
masked := c.Process(user)
// TEXT output: map[id:123 [CENSORED]:[CENSORED] [CENSORED]:[CENSORED]].
// JSON output: {"id":"123","[CENSORED]":"[CENSORED]","[CENSORED]":"[CENSORED]"}.

Nested Maps

Censor processes nested maps to any depth:

userData := map[string]interface{}{
"id": "123",
"contact": map[string]string{
"email": "user@example.com",
"phone": "555-1234",
},
"address": map[string]string{
"street": "123 Main St",
"city": "New York",
"zipCode": "10001",
},
}

// TEXT output: map[id:123 contact:map[email:[CENSORED] phone:[CENSORED]] address:map[street:[CENSORED] city:[CENSORED] zipCode:[CENSORED]]].
// JSON output: {"id":"123","contact":{"email":"[CENSORED]","phone":"[CENSORED]"},"address":{"street":"[CENSORED]","city":"[CENSORED]","zipCode":"[CENSORED]"}}.

Maps with Complex Keys

Censor handles maps with non-string keys:

// Map with integer keys.
userScores := map[int]int{
1: 100,
2: 200,
3: 300,
}

// TEXT output: map[1:100 2:200 3:300].
// JSON output: {"1":100,"2":200,"3":300}.

// Map with struct keys.
type UserID struct {
ID string
Type string
}

userMap := map[UserID]string{
{ID: "123", Type: "customer"}: "John",
{ID: "456", Type: "employee"}: "Jane",
}

// TEXT output: map[{ID:[CENSORED] Type:[CENSORED]}:[CENSORED] {ID:[CENSORED] Type:[CENSORED]}:[CENSORED]].
// JSON output: {"{\"ID\":\"[CENSORED]\",\"Type\":\"[CENSORED]\"}":"[CENSORED]","{\"ID\":\"[CENSORED]\",\"Type\":\"[CENSORED]\"}":"[CENSORED]"}.

Slices and Arrays

Slices and arrays are processed by iterating over each element and processing it according to its type.

Basic Slices

// Slice of strings.
emails := []string{
"user1@example.com",
"user2@example.com",
"user3@example.com",
}

// TEXT output: [[CENSORED] [CENSORED] [CENSORED]].
// JSON output: ["[CENSORED]","[CENSORED]","[CENSORED]"].

// Slice of integers.
scores := []int{100, 200, 300}

// TEXT output: [100 200 300].
// JSON output: [100,200,300].

Slices of Structs

type User struct {
ID string `censor:"display"`
Email string
}

users := []User{
{ID: "1", Email: "user1@example.com"},
{ID: "2", Email: "user2@example.com"},
{ID: "3", Email: "user3@example.com"},
}

// TEXT output: [{ID:1 Email:[CENSORED]} {ID:2 Email:[CENSORED]} {ID:3 Email:[CENSORED]}].
// JSON output: [{"ID":"1","Email":"[CENSORED]"},{"ID":"2","Email":"[CENSORED]"},{"ID":"3","Email":"[CENSORED]"}].

Multidimensional Slices

// 2D slice of strings.
matrix := [][]string{
{"user1@example.com", "password1"},
{"user2@example.com", "password2"},
{"user3@example.com", "password3"},
}

// TEXT output: [[CENSORED] [CENSORED]] [[CENSORED] [CENSORED]] [[CENSORED] [CENSORED]]
// JSON output: [["[CENSORED]","[CENSORED]"],["[CENSORED]","[CENSORED]"],["[CENSORED]","[CENSORED]"]]

Arrays

Arrays are processed the same way as slices:

// Array of strings
var emails [3]string
emails[0] = "user1@example.com"
emails[1] = "user2@example.com"
emails[2] = "user3@example.com"

// TEXT output: [[CENSORED] [CENSORED] [CENSORED]]
// JSON output: ["[CENSORED]","[CENSORED]","[CENSORED]"]

Complete Example

Here's a complete example showing how Censor handles various complex types:

package main

import (
"fmt"
"github.com/vpakhuchyi/censor"
)

func main() {
// Create a Censor instance
c := censor.New()

// Define types
type Address struct {
Street string
City string
ZipCode string
}

type Contact struct {
Email string
Phone string
}

type User struct {
ID string `censor:"display"`
Name string
Contact Contact
Address Address
Tags []string
Metadata map[string]string
}

// Create user data
user := User{
ID: "123",
Name: "John Doe",
Contact: Contact{
Email: "john@example.com",
Phone: "555-1234",
},
Address: Address{
Street: "123 Main St",
City: "New York",
ZipCode: "10001",
},
Tags: []string{"customer", "premium", "active"},
Metadata: map[string]string{
"last_login": "2023-01-01",
"api_key": "sk_live_123456789",
},
}

// Process the data
masked := c.Process(user)
fmt.Printf("%+v\n", masked)
// TEXT output: {ID:123 Name:[CENSORED] Contact:{Email:[CENSORED] Phone:[CENSORED]} Address:{Street:[CENSORED] City:[CENSORED] ZipCode:[CENSORED]} Tags:[[CENSORED] [CENSORED] [CENSORED]] Metadata:map[api_key:[CENSORED] last_login:[CENSORED]]}
// JSON output: {"ID":"123","Name":"[CENSORED]","Contact":{"Email":"[CENSORED]","Phone":"[CENSORED]"},"Address":{"Street":"[CENSORED]","City":"[CENSORED]","ZipCode":"[CENSORED]"},"Tags":["[CENSORED]","[CENSORED]","[CENSORED]"],"Metadata":{"api_key":"[CENSORED]","last_login":"[CENSORED]"}}
}

Next Steps

  1. Learn about Basic Types
  2. Check out Special Types
  3. See Format-Specific handling