-1

Would using encoder.Encode within Go's customized json marshal function be causing goroutine stack overflow?

Please take a look at (enclosed below)
https://go.dev/play/p/KXLH0B20b1u

Using encoder.Encode outside (before line 135 the // os.Exit(0)) and within Go's customized json marshal function (before line 173 the os.Exit(0)) has totally different results.

What I'm trying to do is to customize the JSON marshalling output in Go so that each field entry in the fields array is on a single line while still producing a beautified JSON output for the entire MyForm struct.

Copy of https://go.dev/play/p/KXLH0B20b1u

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "os"
)

type MyForm struct {
    Fields []Field `json:"fields"`
    Button struct {
        Type                 string `json:"type,omitempty"`
        Text                 string `json:"text,omitempty"`
        ImageURL             string `json:"imageUrl,omitempty"`
        Width                string `json:"width,omitempty"`
        Location             string `json:"location,omitempty"`
        LayoutGridColumnSpan int    `json:"layoutGridColumnSpan,omitempty"`
    } `json:"button,omitempty"`
    Title                      string `json:"title,omitempty"`
    Description                string `json:"description,omitempty"`
    Version                    string `json:"version,omitempty"`
    ID                         int    `json:"id,omitempty"`
    MarkupVersion              int    `json:"markupVersion,omitempty"`
    NextFieldID                int    `json:"nextFieldId,omitempty"`
    UseCurrentUserAsAuthor     bool   `json:"useCurrentUserAsAuthor,omitempty"`
    PostContentTemplateEnabled bool   `json:"postContentTemplateEnabled,omitempty"`
    PostTitleTemplateEnabled   bool   `json:"postTitleTemplateEnabled,omitempty"`
    PostTitleTemplate          string `json:"postTitleTemplate,omitempty"`
    PostContentTemplate        string `json:"postContentTemplate,omitempty"`
    LastPageButton             any    `json:"lastPageButton,omitempty"`
    Pagination                 any    `json:"pagination,omitempty"`
    FirstPageCSSClass          any    `json:"firstPageCssClass,omitempty"`
    GfpdfFormSettings          []any  `json:"gfpdf_form_settings,omitempty"`
    LabelPlacement             string `json:"labelPlacement,omitempty"`
    DescriptionPlacement       string `json:"descriptionPlacement,omitempty"`
    ValidationPlacement        string `json:"validationPlacement,omitempty"`
    SubLabelPlacement          string `json:"subLabelPlacement,omitempty"`
    RequiredIndicator          string `json:"requiredIndicator,omitempty"`
    CustomRequiredIndicator    string `json:"customRequiredIndicator,omitempty"`
    CSSClass                   string `json:"cssClass,omitempty"`
    SaveButtonText             string `json:"saveButtonText,omitempty"`
    LimitEntries               bool   `json:"limitEntries,omitempty"`
    LimitEntriesCount          string `json:"limitEntriesCount,omitempty"`
    LimitEntriesPeriod         string `json:"limitEntriesPeriod,omitempty"`
    LimitEntriesMessage        string `json:"limitEntriesMessage,omitempty"`
    ScheduleForm               bool   `json:"scheduleForm,omitempty"`
    ScheduleStart              string `json:"scheduleStart,omitempty"`
    ScheduleEnd                string `json:"scheduleEnd,omitempty"`
    SchedulePendingMessage     string `json:"schedulePendingMessage,omitempty"`
    ScheduleMessage            string `json:"scheduleMessage,omitempty"`
    RequireLogin               bool   `json:"requireLogin,omitempty"`
    RequireLoginMessage        string `json:"requireLoginMessage,omitempty"`
    GravityviewEntryModeration string `json:"gravityview_entry_moderation,omitempty"`
    HoneypotAction             string `json:"honeypotAction,omitempty"`
    ValidationSummary          bool   `json:"validationSummary,omitempty"`
    Deprecated                 string `json:"deprecated,omitempty"`
    SaveEnabled                string `json:"saveEnabled,omitempty"`
    EnableHoneypot             bool   `json:"enableHoneypot,omitempty"`
    EnableAnimation            bool   `json:"enableAnimation,omitempty"`
    GvInlineEditEnable         string `json:"gv_inline_edit_enable,omitempty"`
    FormRevisionsEnabled       string `json:"formRevisionsEnabled,omitempty"`
    Save                       struct {
        Enabled bool `json:"enabled,omitempty"`
        Button  struct {
            Type string `json:"type,omitempty"`
            Text string `json:"text,omitempty"`
        } `json:"button,omitempty"`
    } `json:"save,omitempty"`
    ScheduleStartHour   string `json:"scheduleStartHour,omitempty"`
    ScheduleStartMinute string `json:"scheduleStartMinute,omitempty"`
    ScheduleStartAmpm   string `json:"scheduleStartAmpm,omitempty"`
    ScheduleEndHour     string `json:"scheduleEndHour,omitempty"`
    ScheduleEndMinute   string `json:"scheduleEndMinute,omitempty"`
    ScheduleEndAmpm     string `json:"scheduleEndAmpm,omitempty"`
}

type Field struct {
    Type              string `json:"type,omitempty"`
    ID                int    `json:"id,omitempty"`
    Label             string `json:"label,omitempty"`
    AdminLabel        string `json:"adminLabel,omitempty"`
    IsRequired        bool   `json:"isRequired,omitempty"`
    Size              string `json:"size,omitempty"`
    ErrorMessage      string `json:"errorMessage,omitempty"`
    Visibility        string `json:"visibility,omitempty"`
    Inputs            any    `json:"inputs,omitempty"`
    Description       string `json:"description,omitempty"`
    AllowsPrepopulate bool   `json:"allowsPrepopulate,omitempty"`
    InputMask         bool   `json:"inputMask,omitempty"`
    InputMaskValue    string `json:"inputMaskValue,omitempty"`
    InputMaskIsCustom bool   `json:"inputMaskIsCustom,omitempty"`
    MaxLength         string `json:"maxLength,omitempty"`
    InputType         string `json:"inputType,omitempty"`
    NumberFormat      string `json:"numberFormat,omitempty,omitempty"`
    RangeMin          string `json:"rangeMin,omitempty,omitempty"`
    RangeMax          string `json:"rangeMax,omitempty,omitempty"`
}

func (f Field) MarshalJSON() ([]byte, error) {
    // Marshal each Field into its JSON on a single line
    buffer := &bytes.Buffer{}
    encoder := json.NewEncoder(buffer)
    encoder.SetEscapeHTML(false)
    if err := encoder.Encode(f); err != nil {
        return nil, err
    }
    // Remove the trailing newline added by `Encode`
    result := buffer.Bytes()
    return result[:len(result)-1], nil
}

type Person struct {
    Name    string   `json:"name"`
    Age     int      `json:"age"`
    Hobbies []string `json:"hobbies,omitempty"` // omitempty: omit if empty
}

func main() {
    // Create some sample data
    people := []Person{
        {Name: "Alice", Age: 30, Hobbies: []string{"reading", "hiking"}},
        {Name: "Bob", Age: 25},                          // Hobbies will be omitted in JSON
        {Name: "Charlie", Age: 40, Hobbies: []string{}}, // Hobbies will be omitted in JSON because it is an empty array
    }
    buffer := &bytes.Buffer{}
    encoder := json.NewEncoder(buffer)
    encoder.SetEscapeHTML(false)
    if err := encoder.Encode(people); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    fmt.Println(string(buffer.Bytes()))
    // os.Exit(0)

    // Example data
    data1 := Field{
        Type:       "text",
        ID:         1,
        Label:      "Name",
        AdminLabel: "",
        IsRequired: true,
        Size:       "large",
        InputType:  "text",
    }
    data := MyForm{
        Fields: []Field{
            {
                Type:       "text",
                ID:         1,
                Label:      "Name",
                AdminLabel: "",
                IsRequired: true,
                Size:       "large",
                InputType:  "text",
            },
            {
                Type:       "email",
                ID:         2,
                Label:      "Email",
                IsRequired: true,
                Size:       "medium",
                InputType:  "email",
            },
        },
        Title:       "Example Form",
        Description: "This is an example form",
        Version:     "1.0",
        ID:          123,
    }
    json.MarshalIndent(data1, "", "  ")
    os.Exit(0)

    // Marshal with beautification
    output, err := json.MarshalIndent(data, "", "  ")
    if err != nil {
        log.Fatalf("Error marshaling JSON: %v", err)
    }

    fmt.Println(string(output))
}
2
  • Look at what MarshalIndent does, the indentation is an entirely separate step from marshaling. Modifying the marshaled data does not affect how Indent formats the json Commented Dec 31, 2024 at 23:22
  • Thanks Jim. I noticed such practice/demand while trying to find the solution on my own, and I ended up with writing my own marshaling function for the entire MyForm struct. Commented Jan 1 at 7:33

1 Answer 1

5

Field.MarshalJSON calls Encoder.Encode to marshal the Field value. Encoder.Encode calls Field.MarshalJSON to marshal the Field. This repeats until the stack space is exceeded.

Break the recursion by declaring a new type without the MarshalJSON method. Convert the Field value to that new type and encode that value:

type x Field
if err := encoder.Encode(x(f)); err != nil {
    return nil, err
}

https://go.dev/play/p/hA8dkwgDc-S

Sign up to request clarification or add additional context in comments.

2 Comments

The people and data1 variables are not relevant to the question. OP should delete code related to those variables to make the question easier to understand.
Thanks for the working code. The people variables shows that its whole object is output on a single line, which is what I wanted. However, the code in go.dev/play/p/hA8dkwgDc-S outputs each field entry in the fields array not on a single line, which defeats the whole purpose of using a customized json marshal function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.