{ } Schemato

Guide

How to convert JSON to a Go struct

A practical walkthrough for turning a real JSON payload into Go structs that are ready for encoding/json.

Need it now? JSON → Go struct converter.


Step 1 — Start with a representative JSON sample

A single sample can only infer what it can see. Use a payload that includes nested objects, arrays, null values, and real field names from your API:

{
  "id": 42,
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "is_admin": false,
  "profile": {
    "company": "Analytical Engine Labs",
    "timezone": "Europe/London"
  },
  "tags": ["math", "engine"],
  "last_login_at": null
}

Step 2 — Generate the Go struct

Paste the sample into the JSON → Go struct converter. The first pass gives you the shape and json tags:

type Root struct {
	ID          int      `json:"id"`
	Name        string   `json:"name"`
	Email       string   `json:"email"`
	IsAdmin     bool     `json:"is_admin"`
	Profile     Profile  `json:"profile"`
	Tags        []string `json:"tags"`
	LastLoginAt any      `json:"last_login_at"`
}

type Profile struct {
	Company  string `json:"company"`
	Timezone string `json:"timezone"`
}

Step 3 — Polish names, IDs, and optional fields

Generated structs are a starting point. For production code, rename the root type, use int64 for large IDs, and make truly optional fields pointers:

package model

import "time"

type User struct {
	ID          int64      `json:"id"`
	Name        string     `json:"name"`
	Email       *string    `json:"email,omitempty"`
	IsAdmin     bool       `json:"is_admin"`
	Profile     Profile    `json:"profile"`
	Tags        []string   `json:"tags"`
	LastLoginAt *time.Time `json:"last_login_at,omitempty"`
}

type Profile struct {
	Company  string `json:"company"`
	Timezone string `json:"timezone"`
}

Step 4 — Decode JSON at the API boundary

Once the struct is polished, use the standard library to decode request bodies, fixtures, webhook payloads, or API responses:

func DecodeUser(r io.Reader) (*model.User, error) {
	var user model.User
	if err := json.NewDecoder(r).Decode(&user); err != nil {
		return nil, err
	}
	return &user, nil
}

Step 5 — Review json tags before shipping

Keep tags aligned with the wire format. Add omitempty only when your API should omit zero values during encoding:

b, err := json.Marshal(user)
if err != nil {
	return err
}
fmt.Println(string(b))

Pointers or values?

  • • Use values for required fields where the zero value is meaningful.
  • • Use pointers when you need to know whether a field was missing or null.
  • • Use custom nullable types when database scanning and JSON encoding both matter.

Common pitfalls

FAQ

Should optional fields be pointers?

Usually yes. Pointers distinguish missing or null values from normal zero values like false or 0.

What should I do with null values?

Use pointer fields for nullable scalars, or custom nullable types when database scanning and JSON encoding share the same model.

Can I generate structs from SQL instead?

Yes. Use the SQL to Go struct guide when the source of truth is a CREATE TABLE statement.

Related