풀어 주다: 2023-07-24 15:18:39
앞으로
1272명이 탐색했습니다.
개발 과정에서 데이터 전송 형식으로 JSON을 사용하는 경우가 많습니다. 이는 JSON 데이터의 인코딩 및 디코딩과 분리될 수 없습니다. Go에서는 인코딩/json 패키지가 이러한 기능을 제공합니다.

encoding/json 패키지의 Encoder.Encode() 및 Marshal()을 사용하여 Json 직렬화를 구현할 수 있으며 Decoder.Decode() 및 Unmarshal()을 사용하여 Json 역직렬화를 구현할 수 있습니다.

예제는 다음과 같습니다

package main

import (
 "encoding/json"
 "os"
)

type Metric struct {
 Name  string `json:"name"`
 Value int64  `json:"value"`
}

func main() {
 _ = json.NewEncoder(os.Stdout).Encode(
  []*Metric{
   {"vv", 12},
   {"tz", 9},
   {"ss", 82},
  },
 )
}
로그인 후 복사

Output

[{"name":"vv","value":12},{"name":"tz","value":9},{"name":"ss","value":82}]
로그인 후 복사

질문하기

위 구조를 예로 들면 Metric은 통계적 지표를 나타냅니다. 여기서 Name은 지표 이름이고 Value는 지표 값입니다.

프로그램이 외부 Json 데이터를 수신할 때 아래와 같이 표시 값이 부동 소수점 숫자인 상황이 있다고 가정합니다.

func main() {
 var metric Metric
 err := json.Unmarshal([]byte(`{"name": "tq", "value": 13.14}`), &metric)
 if err != nil {
  panic(err)
 }
 fmt.Println(metric)
}
로그인 후 복사

메트릭 구조에 정의된 값 필드가 int64이므로 Json 데이터의 역직렬화 시 다음 오류가 발생합니다.

panic: json: cannot unmarshal number 13.14 into Go struct field Metric.value of type int64
로그인 후 복사

이 문제를 어떻게 처리해야 합니까? 원래 구조 정의를 변경하지 않고 이 상황을 처리할 수 있습니까?

Solution

encoding/json에는 두 가지 매우 중요한 인터페이스가 있습니다.

// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler interface {
 MarshalJSON() ([]byte, error)
}

// Unmarshaler is the interface implemented by types
// that can unmarshal a JSON description of themselves.
// The input can be assumed to be a valid encoding of
// a JSON value. UnmarshalJSON must copy the JSON data
// if it wishes to retain the data after returning.
//
// By convention, to approximate the behavior of Unmarshal itself,
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
type Unmarshaler interface {
 UnmarshalJSON([]byte) error
}
로그인 후 복사

如果类型 T 实现了 Marshaler 或 Unmarshaler 接口方法,就能自定义了序列化和反序列化规则。

有了这个理解基础,那么对于上文中的问题,我们很自然地想到,让 Metric 结构体实现UnmarshalJSON()。

那么首先想到最简单的方式,是引入一个临时结构体:让其 Value 字段定义为 float64,其他字段维持和原结构体一致。

func (u *Metric) UnmarshalJSON(data []byte) error {
 type tmp struct {
  Name  string  `json:"name"`
  Value float64 `json:"value"`
 }
 t := &tmp{
  Name:  u.Name,
  Value: float64(u.Value),
 }
 err := json.Unmarshal(data, t)
 if err != nil {
  return nil
 }
 // 注意 Unmarshaler 接口定义的以下规则:
 // UnmarshalJSON must copy the JSON data
 // if it wishes to retain the data after returning.
 u.Name = t.Name
 u.Value = int64(t.Value)
 return nil
}
로그인 후 복사

这样做能够解决我们的问题,但并不优雅。

我们可以使用结构体的继承。这样,当结构体存在大量字段时,我们仅定义需要更改的字段即可。

func (u *Metric) UnmarshalJSON(data []byte) error {
 t := &struct {
  Value float64 `json:"value"`
  *Metric
 }{
  Value:  float64(u.Value),
  Metric: u,
 }
 if err := json.Unmarshal(data, &t); err != nil {
  return err
 }
 u.Value = int64(t.Value)
 return nil
}
로그인 후 복사

不过这样子会引出新的问题:继承原结构体的新 strcut 同样会继承原结构体的方法(UnmarshalJSON() 方法),这将无限循环,最终导致堆栈溢出。

fatal error: stack overflow
로그인 후 복사

最佳解决方案是以结构体新类型,让新类型获得原始结构体的所有字段属性,但是却不会继承原有结构体的方法。

func (u *Metric) UnmarshalJSON(data []byte) error {
 type AliasMetric Metric
 t := &struct {
  Value float64 `json:"value"`
  *AliasMetric
 }{
  Value:       float64(u.Value),
  AliasMetric: (*AliasMetric)(u),
 }
 if err := json.Unmarshal(data, &t); err != nil {
  return err
 }
 u.Value = int64(t.Value)
 return nil
}
로그인 후 복사

这样,就完美解决了我们的问题。

总结

在 Json 数据的处理中,我们可以通过为对象实现 Marshaler 或 Unmarshaler 接口方法,从而制定我们想要的序列化和反序列化规则。

本文通过一个简单的示例,展示了如何通过实现结构体的 UnmarshalJSON() 方法,而达到反序列化时更改字段属性的目的。

同理,我们可以为结构体定义 MarshalJSON() 方法,制定序列化规则,这就留给读者自行尝试了。

위 내용은 의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:Go语言进阶学习
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿