Skip to content

JSON to Go Struct

Convert JSON to Go structs with json tags, int64 or float64 per field, and pointers for values that can be missing.

Input · JSON
Output · Go
Valid JSONRuns in your browser

Load from a URL

Your browser fetches it directly, so the server must allow cross-origin requests (CORS).

Weather JSON to Go structs

With Weather as the Root name, the sample weather response becomes:

{
  "location": { "name": "Reykjavik", "lat": 64.1466, "lon": -21.9426 },
  "current": {
    "temp_c": 3.2,
    "wind_kph": 24.1,
    "condition": { "text": "Light rain", "code": 1183 }
  },
  "forecast": [
    {
      "date": "2026-11-06",
      "max_c": 5.1,
      "min_c": -0.4,
      "chance_of_rain": 80
    },
    {
      "date": "2026-11-07",
      "max_c": 4.4,
      "min_c": -1.2,
      "chance_of_rain": 35
    }
  ]
}
package main

type Weather struct {
	Location Location   `json:"location"`
	Current  Current    `json:"current"`
	Forecast []Forecast `json:"forecast"`
}

type Forecast struct {
	Date         string  `json:"date"`
	MaxC         float64 `json:"max_c"`
	MinC         float64 `json:"min_c"`
	ChanceOfRain int64   `json:"chance_of_rain"`
}

type Current struct {
	TempC     float64   `json:"temp_c"`
	WindKph   float64   `json:"wind_kph"`
	Condition Condition `json:"condition"`
}

type Condition struct {
	Text string `json:"text"`
	Code int64  `json:"code"`
}

type Location struct {
	Name string  `json:"name"`
	Lat  float64 `json:"lat"`
	Lon  float64 `json:"lon"`
}

Fields are exported because encoding/json only fills exported fields, and each tag keeps the original key: temp_c becomes TempC with json:"temp_c". Both forecast days merge into one Forecast struct.

int64 or float64

A field is int64 when every sample value is whole, and float64 once one has a decimal part. chance_of_rain was 80 and 35, so it is int64; if the API can send 12.5, make it float64 or json.Unmarshal fails on that field.

Pointers, omitempty and initialisms

After array items are merged (rules on JSON to TypeScript), fields that are null or missing somewhere become pointers with omitempty. With Nodes as the root name:

[
  { "id": 1, "ip": "10.0.0.4", "uptime_sec": 86400, "api_url": "https://n1.example.com" },
  { "id": 2, "ip": "10.0.0.9", "uptime_sec": null, "region": "eu-west" }
]
package main

type Nodes []Node

type Node struct {
	ID        int64   `json:"id"`
	IP        string  `json:"ip"`
	UptimeSec *int64  `json:"uptime_sec,omitempty"`
	APIURL    *string `json:"api_url,omitempty"`
	Region    *string `json:"region,omitempty"`
}

A plain int64 can’t say “not sent”: a missing or null value leaves 0. A nil *int64 can, and omitempty drops it when you marshal. Check before you dereference: if n.Region != nil { fmt.Println(*n.Region) }. Slices and any need no pointer, since nil already means nothing there. Optional objects become pointers too, because omitempty never treats a struct value as empty; Go 1.24’s omitzero does, but older versions lack it.

Initialisms follow Go style: id, ip and api_url become ID, IP and APIURL. The generator knows about two dozen, including URL, HTTP, JSON and UUID.

Decode it

package main

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

func main() {
	data, err := os.ReadFile("weather.json")
	if err != nil {
		log.Fatal(err)
	}
	var w Weather
	if err := json.Unmarshal(data, &w); err != nil {
		log.Fatal(err)
	}
	fmt.Println(w.Location.Name, w.Forecast[0].MaxC)
}

Change package main to your own package. json.Unmarshal skips unknown keys; to reject them, decode with json.NewDecoder and call DisallowUnknownFields() first. invalid character ‘<’ looking for beginning of value means the server sent HTML instead of JSON. JSON to Rust does the same job with serde.

Questions

Why does a field have the type any?

Its values had different JSON types in the sample, such as a number in one item and a string in another, or it was only ever null. Go has no union type, so any takes whatever arrives until you set the real type.

Can a field be time.Time instead of string?

Yes, if every value is a full RFC 3339 timestamp such as 2026-11-04T07:41:00Z, the format time.Time decodes. A bare date like 2026-11-06 fails, so keep it a string or write a small custom type.

What happens to keys with dashes?

The Go name drops the dash and capitalizes each word, so first-name becomes FirstName. The tag keeps the original key, so decoding still matches.

Should I use int instead of int64?

Either works on 64-bit platforms, where int is 64 bits wide. The generator writes int64 so the size is the same on every platform.

Related tools

Esc