jsongostructescapingmarshalling

How to pass raw JSON with spaces and tabs to a struct in Go without extra escaping or marshaling


I'm working with a Go application where I need to pass raw JSON data to a struct field, but I am encountering issues with extra escaping when marshaling the struct, especially when the raw JSON contains spaces and tabs. Specifically, I am using the following structs:

type BrandTemplate struct {
    Type     string `json:"type"`     // Template type (e.g., email_forgot_password)
    Locale   string `json:"locale"`   // Locale (e.g., "es" for Spanish, "en" for English)
    Template string `json:"template"` // Template content (Email/SMS content)
}

type BrandTemplate1 struct {
    Type     string          `json:"type"`
    Locale   string          `json:"locale"`
    Template json.RawMessage `json:"template"` // Use RawMessage to avoid extra escaping
}

The BrandTemplate struct expects the Template field to be a string, but the raw JSON I need to pass contains spaces, tabs, and special characters that require escaping, which I don’t want to escape manually.

On the other hand, the BrandTemplate1 struct uses json.RawMessage, which works perfectly because it preserves the raw JSON format. But I want to use the BrandTemplate struct, and I am wondering if there’s a way to avoid manually escaping the JSON string and passing raw JSON in a clean way while preserving the spaces, tabs, and other formatting.

Here’s an example of what I am trying to do:

// Define the template as raw JSON
template := `{
    "subject": "Email MFA App Verification Code",
    "html": "<html><head></head><body><p>Here is the code: {{otp_code}}</p></body></html>",
    "plain": "Here is the code: {{otp_code}}"
}`

// Create a new BrandTemplate object
brandTemplate := models.BrandTemplate{
    Type:     "email_code_app_verification", // Example template type
    Locale:   "en",                          // Locale for the template (English)
    Template: template,                      // Pass the raw JSON string
}

This is the output I am getting when I print brandTemplate:

BrandTemplate JSON:
{
  "type": "email_code_app_verification",
  "locale": "en",
  "template": "{\n\t\t\"subject\": \"Email MFA App Verification Code\",\n\t\t\"html\": \"\\u003chtml\\u003e\\u003chead\\u003e\\u003c/head\\u003e\\u003cbody\\u003e\\u003cp\\u003eHere is the code: {{otp_code}}\\u003c/p\\u003e\\u003c/body\\u003e\\u003c/html\\u003e\",\n\t\t\"plain\": \"Here is the code: {{otp_code}}\"\n\t}"
}

This is the expected output:

BrandTemplate JSON:
{
    "type": "email_code_app_verification",
    "locale": "en",
    "template": {
        "subject": "Email MFA App Verification Code",
        "html": "<html><head></head><body><p>Here is the code: {{otp_code}}</p></body></html>",
        "plain": "Here is the code: {{otp_code}}"
    }
}

Is there a better way to pass raw JSON with spaces and tabs into the BrandTemplate struct without additional escaping or marshaling? I’ve seen json.RawMessage used in other contexts, but I’d like to avoid that if possible and work directly with the BrandTemplate struct as it is.

Any advice or solutions for this would be greatly appreciated!


Solution

  • Convert the Template field to json.RawMessage in a MarshalJSON method on BrandTemplate.

    type BrandTemplate struct {
        Type     string `json:"type"`   // Template type (e.g., email_forgot_password)
        Locale   string `json:"locale"` // Locale (e.g., "es" for Spanish, "en" for English)
        Template string `json:"-"`      // Template content (Email/SMS content)
    }
    
    func (t *BrandTemplate) MarshalJSON() ([]byte, error) {
        // Define type to break recursive calls to MarshalJSON.
        // The type X has all of fields for BrandTemplate, but
        // non of the methods.
        type X BrandTemplate
    
        // Marshal a type that shadows BrandTemplate.Template
        // with a raw JSON field of the same name.
        return json.Marshal(
            struct {
                *X
                Template json.RawMessage `json:"template"`
            }{
                (*X)(t),
                json.RawMessage(t.Template),
            })
    }
    

    https://go.dev/play/p/cG8kPgltwvw