如何在Go中解析普通和引用的JSON数字?

Ole*_*evs 2 json go

我正在处理第三方基于JSON的API.通常它会用引号括起所有数字,但有时它不会.我无能为力.

我正在尝试提出一个解决方案,解析数字,无论它们是否被引用.标准库提供了一个,string字段标记,它允许将数字字段映射到引用的值,但不幸的是,如果它不在引号中,则它无法处理该值.

func test(s string) {
  err := json.Unmarshal([]byte(s), &struct {
    F1 float64
    F2 float64 `json:",string"`
  }{})
  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Println("success")
}

func main() {
  test(`{"f1": 1.23}`)   // success
  test(`{"f1": "1.23"}`) // cannot unmarshal string into Go value of type float64
  test(`{"f2": 1.23}`)   // invalid use of ,string struct tag, trying to unmarshal unquoted value into ...
  test(`{"f2": "1.23"}`) // success
}
Run Code Online (Sandbox Code Playgroud)

围棋游乐场

有没有解决的办法?

Ole*_*evs 6

正确的解决方案是"克隆"float64 MarshalJSONUnmarshalJSON为其定义自定义:

type jsonFloat64 float64

func (f jsonFloat64) MarshalJSON() ([]byte, error) {
  return json.Marshal(float64(f))
}

func (f *jsonFloat64) UnmarshalJSON(data []byte) error {
  if len(data) >= 2 && data[0] == '"' && data[len(data)-1] == '"' {
    data = data[1 : len(data)-1]
  }

  var tmp float64
  err := json.Unmarshal(data, &tmp)
  if err != nil {
    return err
  }

  *f = jsonFloat64(tmp)
  return nil
}
Run Code Online (Sandbox Code Playgroud)

然后你就可以做这样的事情:

func test(s string) {
  err := json.Unmarshal([]byte(s), &struct {
    F jsonFloat64
  }{})
  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Println("success")
}

func main() {
  test(`{"f": 1.23}`)   // success
  test(`{"f": "1.23"}`) // success
}
Run Code Online (Sandbox Code Playgroud)

围棋游乐场

随意调整UnmarshalJSON您的需求,我的间距非常严格.归功于Dave C,这很大程度上受到了他对另一个问题的评论的启发(其中还包含上述解决方案的更多变体).

或者,您可以使用正则表达式预处理JSON数据,但如果上面的解决方案是可行的选项,则不要这样做,它会更快.

re := regexp.MustCompile(`(":\s*)([\d\.]+)(\s*[,}])`)
rawJsonByteArray = re.ReplaceAll(rawJsonByteArray, []byte(`$1"$2"$3`))
Run Code Online (Sandbox Code Playgroud)