encoding/json.Marshaler în Go

Interfețe pentru a controla singur forma JSON a unui tip: MarshalJSON la scriere, UnmarshalJSON la citire.

Sintaxă
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

Ce face encoding/json.Marshaler

Dacă un tip are metoda MarshalJSON() ([]byte, error) (implementează json.Marshaler), json.Marshal o apelează în loc să folosească regulile obișnuite. Dacă are UnmarshalJSON([]byte) error (implementează json.Unmarshaler), json.Unmarshal îi dă textul JSON brut al valorii și o lasă pe ea să-l interpreteze.

Se folosesc pentru formate speciale: o dată scrisă ca "19.10.2026", o sumă păstrată în bani și scrisă în lei, un enum scris ca text. MarshalJSON are de obicei receptor valoare (ca să funcționeze și pentru valori, nu doar pentru pointeri), iar UnmarshalJSON are receptor pointer, pentru că modifică valoarea.

Ce returnează MarshalJSON trebuie să fie JSON valid; cel mai sigur este să-l produci tot cu json.Marshal. Tipurile care implementează encoding.TextMarshaler (MarshalText) sunt și ele scrise ca text JSON.

Ce returnează

MarshalJSON: textul JSON și o eroare. UnmarshalJSON: o eroare.

Exemple

Exemplul 1: Dată în format românesc

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type DataRo struct{ time.Time }

func (d DataRo) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.Format("02.01.2006"))
}

func (d *DataRo) UnmarshalJSON(b []byte) error {
	var text string
	if err := json.Unmarshal(b, &text); err != nil {
		return err
	}
	t, err := time.Parse("02.01.2006", text)
	if err != nil {
		return fmt.Errorf("dată invalidă %q: folosește ZZ.LL.AAAA", text)
	}
	d.Time = t
	return nil
}

type Factura struct {
	Numar    string `json:"numar"`
	Scadenta DataRo `json:"scadenta"`
}

func main() {
	f := Factura{"F-147", DataRo{time.Date(2026, 11, 18, 0, 0, 0, 0, time.UTC)}}
	out, _ := json.Marshal(f)
	fmt.Println(string(out))

	var g Factura
	err := json.Unmarshal([]byte(`{"numar":"F-148","scadenta":"25.12.2026"}`), &g)
	fmt.Println(g.Numar, g.Scadenta.Weekday(), g.Scadenta.YearDay(), err)

	err = json.Unmarshal([]byte(`{"numar":"F-149","scadenta":"2026-12-25"}`), &g)
	fmt.Println("Eroare:", err)
}

Rezultat

{"numar":"F-147","scadenta":"18.11.2026"}
F-148 Friday 359 <nil>
Eroare: dată invalidă "2026-12-25": folosește ZZ.LL.AAAA

Exemplul 2: Bani păstrați în bani, scriși în lei

Sumele se țin ca întregi (bani), ca să nu apară erori de rotunjire, dar în JSON apar în lei.

package main

import (
	"encoding/json"
	"fmt"
	"math"
)

type Lei int64 // în bani

func (l Lei) MarshalJSON() ([]byte, error) {
	return json.Marshal(float64(l) / 100)
}

func (l *Lei) UnmarshalJSON(b []byte) error {
	var v float64
	if err := json.Unmarshal(b, &v); err != nil {
		return err
	}
	*l = Lei(math.Round(v * 100))
	return nil
}

type Produs struct {
	Nume string `json:"nume"`
	Pret Lei    `json:"pret"`
}

type Cos struct {
	Produs string `json:"produs"`
	Bucati int    `json:"bucati"`
	Total  Lei    `json:"total"`
}

func main() {
	var p Produs
	if err := json.Unmarshal([]byte(`{"nume":"cafea","pret":32.99}`), &p); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("Preț păstrat în bani:", int64(p.Pret))

	c := Cos{Produs: p.Nume, Bucati: 3, Total: p.Pret * 3}
	out, _ := json.Marshal(c)
	fmt.Println(string(out))
}

Rezultat

Preț păstrat în bani: 3299
{"produs":"cafea","bucati":3,"total":98.97}

Greșeli frecvente

  • În MarshalJSON apelezi json.Marshal(d) pe același tip: metoda se apelează pe ea însăși la nesfârșit. Convertește întâi la alt tip (de exemplu type alias T, apoi json.Marshal(alias(d))).
  • Returnezi text care nu este JSON valid, de exemplu []byte(d.Format(...)) fără ghilimele: Marshal dă eroare. Folosește json.Marshal(text).
  • Scrii UnmarshalJSON cu receptor valoare: modificările se fac pe o copie și se pierd.

Comenzi înrudite

  • encoding/json.Marshal: Transformă o valoare Go (structură, map, slice) în JSON compact; etichetele json:”…” aleg numele și regulile câmpurilor.
  • encoding/json.Unmarshal: Citește JSON și completează o valoare Go (structură, map, slice) dată prin pointer; potrivește cheile după etichetele json.
  • encoding/json.RawMessage: Un fragment JSON păstrat ca text brut: decodarea lui se amână (ex. după un câmp „tip”) sau se trimite mai departe neschimbat.
  • time.Time.Format: Transformă un moment în text după un model scris cu data de referință 2 ianuarie 2006, 15:04:05.