Merges array fieldsOptional-field pointersIdiomatic initialisms

JSON to Go Struct Converter

Generate Go structs from a JSON sample, with json tags, nested types, fields merged across array elements and pointers for optional values. All processing happens locally in your browser.

Go type generation

Turn a JSON sample into Go structs

Fields are merged across every element of an array rather than read from the first one, so a key that only appears in later elements is still generated — and marked optional.

Structs1
Fields6
Root typeAutoGenerated
Inference notes1
InputJSON sample
OutputGo structs
type AutoGenerated struct {
	ID int `json:"id"`
	UserName string `json:"user_name"`
	Email string `json:"email"`
	IsActive bool `json:"is_active"`
	Score float64 `json:"score"`
	CreatedAt string `json:"created_at"`
}
Generation options
Nested types are named from their key.
Adds a package line at the top of the file.
JSON has one number type, so this is a choice rather than a detection.

Decode into AutoGenerated with encoding/json.

Fields worth reviewing before you ship this"created_at" looks like a timestamp. It is generated as a string; switch it to a real date type if you want parsing and validation.
Load sampleLoad a sample

What is generated, and why the type is a guess

Go decodes JSON into a struct using the standard encoding/json package. The struct declares the field names and types; a json tag maps each Go field back to the original key:

type User struct {
	ID       int    `json:"id"`
	UserName string `json:"user_name"`
	IsActive bool   `json:"is_active"`
}

The tag matters because Go requires exported fields — starting with a capital letter — for encoding/json to see them, while JSON keys are usually snake_case or camelCase. Without the tag, decoding user_name into UserName silently produces an empty string.

Why inference is inherently partial: JSON has one number type and no concept of optionality. A sample tells you what was present, not what can be present. Generated structs are a strong starting point that you should read before shipping, and the tool lists every field where it had to make a judgement call.

How to generate structs

  1. Paste a representative sample Ideally one that includes optional fields. Every element of an array is inspected, so a key that appears only in the third element is still discovered.
  2. Name the root struct Nested types are named from their key. A customer object becomes a Customer struct.
  3. Choose how whole numbers are typed int, int64, or float64 for everything. See below for why this is a decision rather than a detection.
  4. Read the review notes Null-only fields, empty arrays, conflicting types, oversized integers and date-looking strings are all called out individually.

Field merging across arrays

Most JSON-to-Go tools read the first element of an array and stop. That produces a struct that fails on real data. Given this input:

{
  "items": [
    { "sku": "AB-100", "qty": 2 },
    { "sku": "CD-200", "qty": 1, "gift_note": "Happy birthday" },
    { "sku": "EF-300", "qty": 4, "gift_note": null }
  ]
}

A first-element-only tool generates a struct with SKU and Qty, and gift_note disappears entirely. Every element is inspected here, so the field is found and marked optional because it is absent from one of the three:

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

Why the pointer

Go has no null. An absent string and an empty string both give you "", so a plain string field cannot tell you which happened. A *string can: nil means the key was absent, and a pointer to "" means it was present and empty.

That distinction matters for PATCH endpoints, where "field not supplied" and "field set to empty" must behave differently. It costs an extra dereference everywhere else, so the option is there to turn it off.

Why omitempty

omitempty only affects encoding, not decoding. With it, a zero value is left out of the output JSON. Note the well-known trap: omitempty treats false, 0 and "" as empty, so a genuine false disappears from encoded output. Combining omitempty with a pointer is the usual way to keep that distinction.

Numbers: the decision JSON forces on you

JSON has exactly one number type. There is no integer, no float, no decimal — just number. So {"price": 10} and {"price": 10.0} are byte-different but parse identically, and no tool can tell them apart after parsing.

Sample value Inferred as Risk
42 int Decoding fails if a later payload sends 42.5
10.0 int Parses to the integer 10; the decimal intent is lost
91.5 float64 Correct
9007199254740993 int64 Promoted automatically; beyond JavaScript's exact range
19.99 as money float64 Binary float rounding on repeated arithmetic

Two practical rules follow. For a field that could be fractional even if your sample happens to be whole, set it to float64 by hand. For money, neither int nor float64 is right — store minor units as an integer, or use a decimal library.

There is a third option worth knowing: json.Number keeps the literal text and defers the decision, which is useful when you need to round-trip a payload without altering its numbers.

Examples of naming decisions

Go has firm naming conventions, and the generator follows them rather than doing a plain capitalisation.

JSON key Go field Rule applied
user_name UserName Underscore separates words
createdAt CreatedAt camelCase boundaries are detected too
id ID Initialisms are fully capitalised
api_url APIURL Two initialisms in sequence
HTTPStatus HTTPStatus Acronym boundary preserved, not split into HTTPStatusHttpStatus
request-id RequestID Hyphens are separators; a hyphen is invalid in an identifier
2fa_enabled F2faEnabled An identifier cannot start with a digit, so a prefix is added

The initialism list is the one from the Go project's own code review comments — ID, URL, API, HTTP, JSON, SQL, UUID and around thirty more. Writing Id and Url is legal Go but reads as non-idiomatic to reviewers.

Identical object shapes are deduplicated: if billing_address and shipping_address have the same keys, one Address-style struct is generated and both fields use it. Different shapes that happen to share a key name get numbered names instead of silently colliding.

What you should still change by hand

  • Timestamps. "2026-03-14T09:00:00Z" is generated as string. Change it to time.Time and encoding/json will parse RFC 3339 automatically. A non-RFC-3339 format needs a custom UnmarshalJSON.
  • Enumerated values. A status field of "active" is a string here. A named type with constants gives you compile-time safety.
  • Null-only fields. Generated as any, because a field that is null in every sample reveals nothing about its real type.
  • Empty arrays. [] becomes []any. Supply a sample containing at least one element to get a real element type.
  • Fields with conflicting types. If one element has "count": 5 and another "count": "5", no single Go type fits. The field becomes any and is flagged. This usually indicates an API inconsistency worth reporting.
  • Struct placement. Generated types are emitted parents-first in one block. Split them across files as your package layout requires.

Use cases

  • Consuming a third-party API. Capture one response and get a decodable struct without hand-typing forty fields.
  • Writing a webhook handler. Paste a sample delivery payload and you have the request body type.
  • Typing a config file. A JSON config becomes a struct you can unmarshal at startup and validate.
  • Building test fixtures. Generate the type, then construct instances in tests instead of embedding raw JSON strings.
  • Auditing an undocumented payload. The struct is a compact schema summary, and the review notes point at the fields that vary between records.
  • Migrating from map[string]any. Replace untyped map access with a real struct and let the compiler find the mistakes.

Working in a different language? JSON to Java generates classes or records from the same model. For the JSON itself, the JSON Formatter validates and indents, and the JSON Schema Validator checks it against a schema.

Privacy

Parsing and code generation run entirely in your browser. The JSON is not uploaded, which matters when the sample is a real API response containing customer data. Usage analytics record only the number of generated structs and the number-type setting, never the JSON or the output.

Frequently Asked Questions

Why is a field a pointer such as *string?

Because it was absent from at least one element of an array in your sample, making it optional. Go has no null, so a plain string cannot distinguish "key absent" from "key present but empty" — both give you "". A pointer can: nil means absent. Turn the option off if you do not need that distinction.

Why did my decimal field become int?

Because the sample value happened to be a whole number, and JSON does not distinguish 10 from 10.0 — both parse to the integer 10. If the field can ever be fractional, change it to float64 by hand, or switch the number option to float64 for everything.

Will a date string become time.Time?

No, it is generated as string and flagged in the review notes. Changing it to time.Time is usually right and encoding/json parses RFC 3339 automatically. It is not done for you because a format such as "14/03/2026" would then fail at runtime rather than obviously at generation time.

What does omitempty actually do?

It affects encoding only, omitting the field when its value is the zero value. The trap is that false, 0 and "" all count as empty, so a deliberate false vanishes from your output JSON. Pairing omitempty with a pointer type preserves the distinction.

Why is one field typed as any?

Either it was null in every sample, or different elements gave it different types. Both cases mean no single concrete type fits what was observed. Conflicting types usually point at an API returning a number in one record and a string in another, which is worth raising with whoever owns it.

Why is ID capitalised but Qty is not?

ID is in Go's conventional initialism list, alongside URL, API, HTTP, JSON, SQL and UUID. Qty is an abbreviation but not an initialism, so it follows normal PascalCase. Writing Id would compile fine but reads as unidiomatic in review.

Do I need to keep the json tags?

Yes, whenever the JSON key is not an exact case-insensitive match for the Go field name. encoding/json does match case-insensitively, so Name would find "name", but UserName will not find "user_name". Keeping the tags makes the mapping explicit and survives a later rename.