ちなみに

火曜日の空は僕を押しつぶした。

golang で Invalid な Json をパースした話

言い訳

社内のチャットログをごにょごにょするのに Windows で動かしたかったので golang に入門した。 2時間位しか触っていないのでおかしなことをしている可能性が高い。 文字コードまわりよくわかってないので知ったかぶりしている。

問題

言語仕様を分かっていないのでサンプルを切り貼りしながら書いて、ミニマムな Json だったらパースできるのに、全体をパースしたらエラーがでるという問題にぶつかった。

{
  "message": "じゃがりこ食べたいなあ"
}

はパースできるのに

{
  "message": "とにかく
最高!"
}

はパースできない。

package main;

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

type Msg struct {
  Message string
}

func main() {
  b := []byte(`{"message": "とにかく
最高!"}`)
  var m Msg
  err := json.Unmarshal(b, &m)
  if err != nil {
    fmt.Fprintln(os.Stderr, err)
  } else {
    fmt.Println(m)
  }
}
$ go run main.go
invalid character '\n' in string literal

原因

Json の仕様的に文字列の中に U+000A を含んではいけないみたい。 golangencoding/json ライブラリは厳密にパースしているのでエラーになってしまう。

\n represents the line feed character (U+000A).

see: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf

試行錯誤

U+000A\n に置き換えていけばいいのかと思ったけど、扱う Json は複数行になるため、文字列中じゃない改行も \n に置き換えてしまってうまくいかない。

{
  "message": "鴨川でビール飲みたい",
  "message": "鴨川じゃなくてもいいので
いますぐ飲みたい"
}

これを変換すると以下のようになってしまう。

{\n  "message": "鴨川鴨川でビール飲みたい",\n  "message": "鴨川じゃなくてもいいので\nいますぐ飲みたい"\n}\n

うまく文字列の中だけを変換したかったけど golang 力が足りなくて解決できなかった。

妥協案

結局いい方保がみつからなかったので、とりあえず Json として Valid になるように、改行をスペースに置き換えることにした。

package main;

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

type Msg struct {
  Message string
}

func main() {
  s := `{"message": "とにかく
最高!"}`
  b := []byte(strings.Replace(s, "\n", " ", -1))
  var m Msg
  err := json.Unmarshal(b, &m)
  if err != nil {
    fmt.Fprintln(os.Stderr, err)
  } else {
    fmt.Println(m)
  }
}

strings ライブラリを使って "\n" (= LF) を " " に置き換えている。

まとめ

  • Json では文字列中に 改行 (U+000A) を含んではいけない
  • 回避するために 改行 (U+000A) を スペース (U+0020) に置き換えた
  • 文字列中の 改行 (U+000A) のみ \n に置き換えられるとベスト

その他

golang で書いたら簡単にクロスコンパイルできて、バイナリを渡すだけで実行できるのでプログラミングに詳しくない人に配布するのにもべんり。