How to Convert JSON to Go Struct (serde-Style Guide)
Looking for a JSON to Go struct generator? Here's the path from sample to typed code. You ship a Go service that consumes a third-party JSON API. Vendor docs are out of date; production payloads have fields the docs forgot. Hand-typing the structs from sample data is error-prone busywork. Generate them.
In this guide you'll learn how to convert JSON to a Go struct using PDFFlare's JSON to Go tool — how encoding/json uses struct tags to map keys, why exported fields with tags are the idiomatic pattern, how the inference picks int vs float64, and the cases where you'll want to refine manually.
Why Struct Tags Matter
encoding/json matches JSON keys to struct fields by name unless a json:"original_key" tag tells it otherwise. Idiomatic Go uses PascalCase exported field names (so the package outside the file can see them); the tag preserves the original snake_case (or any-case) JSON key. Without the tag, snake_case JSON never binds to PascalCase Go.
How to Convert JSON to Go (Step by Step)
- Open PDFFlare's JSON to Go tool.
- Paste a JSON sample. Real production payload — samples often miss optional fields.
- Click Convert to Go. One
structper unique shape;json:"..."tags on every field; type inference for ints vs floats. - Drop into your project.
json.Unmarshal(data, &v)binds the bytes to your struct.
Real Use Cases
Typing a third-party REST API
Hit the endpoint, copy the response, paste here, get accurate Go types. Works whether the upstream is documented or not.
Webhook handlers
Stripe, GitHub, and similar services document webhooks as JSON examples. Convert each example to a Go struct so handlers stay compile-time safe.
Migrating from a dynamic language
Porting a Python or Node service to Go? Use real production payloads to seed type definitions instead of guessing field names.
CLI tools that consume JSON
Building a CLI that pipes JSON from another tool? Generate the input schema as Go structs in seconds.
Common Mistakes (and How to Avoid Them)
- Forgetting
omitempty.Add it manually to fields you don't want emitted when zero (e.g.json:"name,omitempty"). - Using float64 for currency. Refine to a decimal type (
shopspring/decimalis common) for money fields. - Treating mixed-type arrays as a final answer. They become
[]interface; refine to a typed wrapper if you know the shape. - Renaming fields without updating tags. The field name changes affect Go reflection but the JSON tag controls wire format. Update both consciously.
Privacy: Your JSON Stays Local
Conversion runs in your browser. Safe for production payloads.
Related Workflows in the JSON Suite
Adjacent tools you might find useful while working on the same JSON document: the JSON to Rust and JSON Schema both pair well with the conversion above. The first handles a different output format that consumers of your data may prefer; the second covers the validation side of the same workflow.
Related Tools
- JSON to Rust — same idea with serde derive macros.
- JSON to TypeScript — matching front-end types.
- JSON to Protobuf — Go has excellent gRPC support; bring the same payload to Protobuf for binary speed.
- JSON Formatter — pretty-print before converting.
Idiomatic Go Patterns for JSON Handling
Go has a long tradition of pragmatic JSON handling, and a generated struct is a starting point that benefits from some idiomatic refinements. The first refinement is naming: Go convention is short, descriptive names, often with no articles (User, not TheUser; Order, not OrderInfo). The generator preserves whatever names appear in the source data, which often includes prefixes or suffixes that read fine in JavaScript or Python but feel wordy in Go. Rename aggressively after generation; idiomatic names make Go code feel native rather than ported.
The second refinement is interface design. Generated structs are concrete types. Production Go often uses interfaces to decouple consumers from concrete types, enabling testability and flexibility. Once your generated types stabilize, ask which behaviors they support and whether those behaviors should be expressed as interfaces. This is not always worthwhile — interfaces add indirection — but for code that touches multiple data sources or has complex testing needs, the indirection pays off.
The third refinement concerns error handling. The standard encoding/json package returns errors when decoding fails, but the errors are notoriously generic. Production Go services usually wrap these errors with context using fmt.Errorf and the percent-w verb. After decoding, validate domain invariants and return rich errors that explain not just that decoding failed but what specifically was wrong with the input. This investment in error messages pays back many times over when something breaks in production and you need to diagnose from logs.
Finally, consider streaming for large payloads. The generator's output works well with json.Unmarshal, which loads the entire document into memory. For megabytes of JSON, streaming with json.Decoder is more memory-efficient and lets you start processing before the full document arrives. The generator's struct shape stays the same; only the decode call changes. Knowing this option exists matters when payload sizes grow unexpectedly.
Production Patterns for JSON to Go Structs
A generated struct is a starting point. Production-grade Go code adds:
Pointer vs Value for Optional Fields
Go's zero values (0, "",false) are indistinguishable from missing JSON fields. For genuinely optional fields, switch the type to a pointer (*int, *string) — nownil means missing, value means present.
Custom UnmarshalJSON for Edge Cases
When the API uses inconsistent shapes (a field that's sometimes a string, sometimes a number), implement UnmarshalJSON([]byte) error on the struct. Inside, try each shape in order. Cleaner than map[string]interface{} spelunking.
Use Tags for Multi-Format Marshaling
The generator emits json:"..." tags. If you also serialize to YAML or BSON (MongoDB), add those tags alongside: json:"name" bson:"name". One struct serves multiple wire formats.
When to Use a Different Approach
A few alternatives to know:
- For protobuf-based services, generate a proto schema with JSON to Protobuf — protoc emits Go code with full proto3 semantics.
- For OpenAPI-driven workflows, generate from the spec instead — full coverage of every endpoint.
- For schema-first validation, generate a JSON Schema first, then point a Go schema generator at it.
Common Mistakes to Avoid
- Using
interface{}as a catch-all. Tempting when JSON shape varies, but loses type safety. Use a custom UnmarshalJSON or a discriminated-type pattern instead. - Forgetting
omitempty. When serializing back, empty fields show up in the output unless taggedjson:"name,omitempty". The generator can emit this; remember to enable it. - Treating numbers as
float64by default.Go's json package decodes all numbers asfloat64by default. If your IDs are int64-shaped, usejson.Numberor unmarshal into an explicitint64field. - Skipping validation. The decoder happily accepts a payload with missing required fields. Validate after Unmarshal — go-playground/validator with struct tags (
validate:"required") is the standard. - Mismatching exported field names. Go field names must be capitalized to be visible to the json package. The generator does this correctly; if you rename a field to lowercase by mistake, decoding silently fails for that field.
Real-World Use Cases
- HTTP handler request/response binding. Generate the request body struct, decode into it in your handler, type-safe end to end.
- SDK clients for third-party APIs. Stripe, GitHub, etc. — generate matching Go structs from their documented payloads, ship as a small package.
- Replacing
map[string]interface{}spelunking. Existing handlers casting through the generic map are fragile; replace with a typed struct and the compiler catches refactor breakage. - Cross-service contracts. Service A emits JSON; Service B consumes it. Generate matching Go structs from a shared sample, version-control them, both services stay in sync.
Polishing the Generator's Output
Go encourages a particular style of working with data: small, focused types, explicit error handling, and a preference for composition over inheritance. Whatever your specific use case, treat the generated output as a draft that deserves a careful read-through. Generators are excellent at producing the mechanical structure of an artifact and not at the editorial decisions that make the difference between something a colleague will tolerate and something a colleague will appreciate. Read every section of the output the way you would read a piece of writing you were proofreading for a friend. Look for inconsistent naming, missed opportunities to consolidate similar items, and places where the structure is mechanically correct but conceptually awkward. The five minutes spent on this review are the difference between an artifact that pays back over months and one that needs a second pass before it can be used. The generator handles the heavy lifting; you handle the polish that turns a draft into a deliverable. This division of labor is what makes generated code worthwhile in the first place. Without that final pass of human editorial judgment, the generator's output is merely fast rather than valuable, and the value matters more than the speed in nearly every real production setting.
The same logic applies to documentation, comments, and inline context that your generator output rarely supplies. A generated artifact has structure but no narrative; the narrative is what makes the thing useful to the next person who reads it. Add the few sentences of context that explain why a particular choice was made, what the surrounding system expects, and what the next person should look out for. These small editorial gestures cost almost nothing in the moment and pay back many times over when someone is trying to understand what you produced months later. Treat generation as the first ten percent of the work and these editorial passes as the remaining ninety percent that turns mechanical output into something a colleague will reach for again and again. Build the habit early and the gap between your generated artifacts and hand-written ones gets very small over time, which is the real prize.
Wrapping Up
A clean Go struct with json tags is the start of every well-typed JSON consumer. PDFFlare's JSON to Go tool generates it in two clicks; refine for omitempty, decimal types, and stronger array element types where the inference can't.