Интерфейс json.Unmarshaler

Интерфейс json.Unmarshaler находится в пакете encoding/json и позволяет реализовать кастомную логику десериализации JSON-данных в пользовательский тип. Интерфейс содержит единственный метод UnmarshalJSON, который принимает срез байт (JSON-данные) и возвращает ошибку. Если ваш тип реализует этот интерфейс, то при вызове json.Unmarshal будет использоваться ваша реализация метода вместо стандартной.

Синтаксис

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

Для реализации интерфейса необходимо определить метод UnmarshalJSON с указанным синтаксисом. Метод получает исходные JSON-данные в виде байтового среза и должен заполнить поля структуры, используя собственную логику разбора.

Пример

Давайте реализуем кастомную десериализацию для структуры, которая хранит дату в строковом формате:

gopackage main

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

type Person struct {
  Name string
  Age  int
  City string
}

func (p *Person) UnmarshalJSON(data []byte) error {
  var raw map[string]interface{}
  if err := json.Unmarshal(data, &raw); err != nil {
    return err
  }

  if name, ok := raw["name"].(string); ok {
    p.Name = strings.ToUpper(name)
  }
  if age, ok := raw["age"].(float64); ok {
    p.Age = int(age)
  }
  if city, ok := raw["city"].(string); ok {
    p.City = strings.ToUpper(city)
  }
  return nil
}

func main() {
  jsonData := []byte(`{"name":"alex","age":25,"city":"moscow"}`)
  var p Person
  err := json.Unmarshal(jsonData, &p)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  fmt.Printf("Name: %s, Age: %d, City: %s\n", p.Name, p.Age, p.City)
}

Результат выполнения кода:

go"Name: ALEX, Age: 25, City: MOSCOW"

В этом примере метод UnmarshalJSON преобразует все строковые поля структуры в верхний регистр в процессе десериализации. Обратите внимание, что метод должен быть определен на указателе на структуру.

Пример

Рассмотрим пример с обработкой нестандартного формата данных, где необходимо преобразовать строку в числовой тип:

gopackage main

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

type Product struct {
  ID  string
  Price int
}

func (p *Product) UnmarshalJSON(data []byte) error {
  var raw map[string]interface{}
  if err := json.Unmarshal(data, &raw); err != nil {
    return err
  }

  if id, ok := raw["id"].(string); ok {
    p.ID = id
  }
  if priceStr, ok := raw["price"].(string); ok {
    price, err := strconv.Atoi(priceStr)
    if err != nil {
      return err
    }
    p.Price = price
  }
  return nil
}

func main() {
  jsonData := []byte(`{"id":"p001","price":"1500"}`)
  var product Product
  err := json.Unmarshal(jsonData, &product)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  fmt.Printf("ID: %s, Price: %d\n", product.ID, product.Price)
}

Результат выполнения кода:

go"ID: p001, Price: 1500"

В этом примере мы десериализуем JSON, где поле price передается как строка, но структура ожидает числовой тип. Метод UnmarshalJSON выполняет преобразование с помощью функции strconv.Atoi.

Пример

Рассмотрим обработку ошибок при десериализации с использованием интерфейса json.Unmarshaler:

gopackage main

import (
  "encoding/json"
  "fmt"
)

type User struct {
  Username string
  Email  string
}

func (u *User) UnmarshalJSON(data []byte) error {
  var raw map[string]interface{}
  if err := json.Unmarshal(data, &raw); err != nil {
    return err
  }

  username, ok := raw["username"].(string)
  if !ok || username == "" {
    return fmt.Errorf("username is required")
  }
  u.Username = username

  email, ok := raw["email"].(string)
  if !ok || email == "" {
    return fmt.Errorf("email is required")
  }
  u.Email = email

  return nil
}

func main() {
  jsonData := []byte(`{"username":"john","email":"john@example.com"}`)
  var user User
  err := json.Unmarshal(jsonData, &user)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  fmt.Printf("Username: %s, Email: %s\n", user.Username, user.Email)
}

Результат выполнения кода:

go"Username: john, Email: john@example.com"

В этом примере метод UnmarshalJSON проверяет наличие обязательных полей и возвращает ошибку, если данные некорректны. Это позволяет выполнять валидацию данных непосредственно в процессе десериализации.

Смотрите также

  • интерфейс json.Marshaler,
    который позволяет реализовать кастомную сериализацию
  • функцию json.Unmarshal,
    которая выполняет десериализацию JSON-данных
  • функцию json.Marshal,
    которая выполняет сериализацию данных в JSON
  • функцию json.Valid,
    которая проверяет корректность JSON-данных