JSON manipulation in Go
Explore ways to handle JSON data in Golang

full stack dev | SaaS | AI | maker - builder
Search for a command to run...
Explore ways to handle JSON data in Golang

full stack dev | SaaS | AI | maker - builder
No comments yet. Be the first to comment.
Because sometimes your models need to gossip with each other

Whether you are a SysAdmin or Causal Linux (Ubuntu/Debian) User, here is a Guide to Rescue Your Development Environment Without Breaking a Sweat

Because Not All Code Guards Need to Break the Bank

Harnessing the Power of Pydantic for Seamless AI Integration

From Simple Chat-bots to Autonomous Digital Assistants

Dealing with JSON manipulation in Go can involve several approaches, depending on the complexity of your JSON data, the desired performance, and how strict you want to be with your data structures. Below are some common methods:
The most idiomatic way to handle JSON in Go is by mapping JSON data directly to strongly-typed Go structs. This provides compile-time safety and clear structure.
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
// Encode (marshal)
person := Person{Name: "John", Age: 30, Email: "john@example.com"}
jsonData, _ := json.Marshal(person)
// Decode (unmarshal)
var decodedPerson Person
json.Unmarshal(jsonData, &decodedPerson)
Strong typing: Fields are directly mapped to Go types.
Validation: Go's type system ensures data is correctly structured.
Compile-time checks: Errors in field types are caught early.
map[string]interface{} for Dynamic or Unknown JSONIf you are dealing with dynamic or unknown JSON structures (like in APIs or flexible schemas), you can use a map[string]interface{} to handle arbitrary key-value pairs.
var data map[string]interface{}
jsonStr := `{"name": "John", "age": 30, "email": "john@example.com"}`
json.Unmarshal([]byte(jsonStr), &data)
fmt.Println(data["name"], data["age"]) // Output: John 30
Flexibility: Suitable for JSON with unknown or dynamic structures.
Ad-hoc manipulation: You can dynamically access and modify fields.
Lack of type safety: You need to perform type assertions manually.
Error-prone: More runtime errors may occur, e.g., wrong type assumptions.
RawMessage for Deferred JSON ParsingFor partial parsing or when you need to manipulate a portion of JSON without fully unmarshaling it, you can use json.RawMessage. This allows delaying or conditionally parsing part of the JSON data.
type Envelope struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
// Partial decode
jsonStr := `{"type": "person", "data": {"name": "John", "age": 30}}`
var env Envelope
json.Unmarshal([]byte(jsonStr), &env)
if env.Type == "person" {
var person Person
json.Unmarshal(env.Data, &person)
fmt.Println(person.Name, person.Age) // Output: John 30
}
Lazy parsing: You can avoid parsing unnecessary parts of the JSON.
Flexibility: Great for handling complex APIs with different payloads.
For more advanced JSON handling, such as querying or mutating JSON paths, you can use libraries like:
github.com/tidwall/gjson (for fast JSON querying)
github.com/buger/jsonparser (for efficient JSON parsing)
github.com/yalp/jsonpath (for JSONPath expressions)
gjson:import "github.com/tidwall/gjson"
jsonStr := `{"name": "John", "age": 30, "address": {"city": "New York"}}`
name := gjson.Get(jsonStr, "name").String() // Get "John"
city := gjson.Get(jsonStr, "address.city").String() // Get "New York"
fmt.Println(name, city)
Efficiency: Some libraries are faster and optimized for large JSON data.
Path queries: Libraries like gjson allow querying specific fields with dot notation.
json.DecoderFor handling very large JSON data or when working with streams, Go’s json.Decoder can be used to read the JSON incrementally without loading it entirely into memory.
file, _ := os.Open("large.json")
defer file.Close()
decoder := json.NewDecoder(file)
for decoder.More() {
var person Person
decoder.Decode(&person)
fmt.Println(person.Name, person.Age)
}
Memory-efficient: Useful for large JSON data that cannot fit into memory at once.
Streaming: Allows you to process JSON as it's being read.
You can define custom JSON encoding/decoding behavior by implementing the json.Marshaler and json.Unmarshaler interfaces. This is useful when you need fine-grained control over how JSON is generated or parsed.
type CustomPerson struct {
Name string
Age int
}
// Custom unmarshal
func (cp *CustomPerson) UnmarshalJSON(data []byte) error {
var aux struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := json.Unmarshal(data, &aux)
if err != nil {
return err
}
cp.Name = strings.ToUpper(aux.Name) // Custom logic: make the name uppercase
cp.Age = aux.Age
return nil
}
Custom behavior: Allows special formatting or validation rules during marshaling/un-marshaling.
Encapsulation: Keeps complex logic within the struct.
For structured JSON data: use struct-based encoding/decoding.
For dynamic or flexible JSON: use map[string]interface{}.
For partial or deferred parsing: use json.RawMessage.
For very large JSON or streams: use json.Decoder.
For advanced querying or manipulation: consider third-party libraries like gjson.
For custom handling: implement custom marshaling/un-marshaling.
Choose the method based on your specific needs—performance, flexibility, and type safety.
Image attribution