DevToolsmith

JSON to Go Struct Converter

Paste JSON to generate Go structs with json tags. Field names follow Go conventions (UserID, APIURL), optional keys get omitempty, nullable values become pointers, and timestamps can map to time.Time.

Runs 100% in your browser — nothing is uploaded

JSON
Tip: drop a file here361 chars · 361 bytes · 14 lines
Go structs
618 chars · 618 bytes · 24 lines

Tip: paste an array of several objects — keys missing from some of them become optional (omitempty).

Example

JSON input
{
  "order_id": 5012,
  "customer": {
    "id": "c_88",
    "email": "lin@example.com"
  },
  "total": 59.9,
  "paid_at": null,
  "lines": [
    {
      "sku": "TSHIRT-M",
      "qty": 2
    },
    {
      "sku": "MUG",
      "qty": 1,
      "gift_note": "Happy birthday!"
    }
  ]
}
Generated Go (root name: Order)
type Order struct {
	OrderID  int      `json:"order_id"`
	Customer Customer `json:"customer"`
	Total    float64  `json:"total"`
	PaidAt   any      `json:"paid_at"`
	Lines    []Line   `json:"lines"`
}

type Customer struct {
	ID    string `json:"id"`
	Email string `json:"email"`
}

type Line struct {
	SKU      string `json:"sku"`
	Qty      int    `json:"qty"`
	GiftNote string `json:"gift_note,omitempty"`
}

A few things to notice:

  • order_id became OrderID, using the standard Go initialism, not OrderId.
  • paid_at was always null, so its type is any. Change it to *time.Time if you know it's a timestamp.
  • gift_note appears in only one line item, so it gets omitempty.
  • Identical object shapes share one struct instead of being duplicated.

Decoding JSON with the generated structs

var order Order
if err := json.Unmarshal(body, &order); err != nil {
	return fmt.Errorf("decode order: %w", err)
}
fmt.Println(order.Customer.Email, len(order.Lines))

// Encoding back: fields tagged omitempty are skipped when empty
out, _ := json.MarshalIndent(order, "", "  ")

Type mapping

JSONGo
stringstring (or time.Time for RFC 3339 timestamps)
integerint
decimal numberfloat64
true / falsebool
objectnamed struct, or an anonymous struct when inlined
array[]T ([]any when empty or mixed)
sometimes null*T
always null / mixed typesany

Named vs. inline structs

By default every nested object gets its own named type, which you can reuse and test separately. Enable Inline nested structs to produce a single type with anonymous nested structs instead. That's handy for one-off API responses you only decode in one place.

Tips

  • Large integers: IDs above 253 are safe in Go's int64, but if the same JSON is consumed by JavaScript, have the API send them as strings and use json:",string".
  • Unknown fields: encoding/json ignores keys that aren't in your struct. Use decoder.DisallowUnknownFields() to reject them.
  • Working in other languages too? Generate TypeScript interfaces or Pydantic models from the same JSON.

Frequently asked questions

How do I convert JSON to a Go struct?

Paste a JSON sample into the tool above. It creates a struct for every object, picks Go types (string, int, float64, bool, slices), converts keys into exported Go field names, and adds json tags so encoding/json maps the original keys. Copy the result into your .go file.

Why are the field names different from my JSON keys?

Go only encodes and decodes exported fields, which must start with an uppercase letter. The generator converts keys like user_id to UserID, following Go naming conventions for initialisms (ID, URL, HTTP, API). The json:"user_id" tag keeps the mapping to the original key.

When should I use pointers for nullable fields?

Use a pointer (*string, *int) when you need to tell "null or missing" apart from the zero value. A plain int cannot distinguish 0 from null. Pointers are enabled by default for values that are null in your sample. Slices are left as-is because a nil slice already represents null.

What does omitempty do?

With json:"name,omitempty", json.Marshal skips the field when it has its zero value: "", 0, false, nil or an empty slice or map. The generator adds it to keys that are missing from some of the sample objects, because they are optional in the data.

Why is a field typed as any?

The value was always null, or it had different types in different samples (for example a number in one object and a string in another), so no single Go type fits. Replace any with the right type, or with json.RawMessage to decode it later.