2021年12月26日日曜日

GoのJSON操作でハマったのでなんとかしてみる

Goで作ったJSON文字列をPHPに食わせたらエラーになったので、そのへんのまとめです。

ちなみに上の表現はちょっと正確ではないんですが、まあ気にしないで下さい。あとでわかります。

Goのコード

こんな感じのコードでJSONデータを作りました。

package main

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

func main() {
	// 時間のフィールドを持つ構造体
	type TestJson struct {
		Datetime time.Time `json:"datetime"`
	}

	// 現在時刻を入れてJSON文字列を生成
	tj := TestJson{
		Datetime: time.Now(),
	}
	j, _ := json.Marshal(&tj)

	fmt.Println(string(j))
}

これを実行すると、例えばこんな結果が出力されます。

{"datetime":"2021-12-25T19:02:50.713264576+09:00"}

PHPのコード

このJSON文字列を、PHPで解析します。

<?php
// JSONデータをデコード
$data = '{"datetime":"2021-12-25T19:02:50.713264576+09:00"}';
$decoded = json_decode($data);

// 日付を解析
$datetime = date_parse_from_format(DATE_RFC3339_EXTENDED, $decoded->datetime);
print_r($datetime);

これを実行すると、こんな結果が出力されます。

Array
(
    [year] => 2021
    [month] => 12
    [day] => 25
    [hour] => 19
    [minute] => 2
    [second] => 50
    [fraction] => 0.713
    [warning_count] => 0
    [warnings] => Array
        (
        )

    [error_count] => 1
    [errors] => Array
        (
            [23] => The timezone could not be found in the database
        )

    [is_localtime] => 1
    [zone_type] => 0
)

タイムゾーンが見つからんと言われて日付の解析に失敗しました。JSON解析自体は成功しているので最初の文章は正確ではありません。

原因

Goが出力するJSON文字列は日付がナノ秒単位で出ているのですが、PHPの日付解析関数ではナノ秒に対応していないのが原因です。

対策その1

1つ目の対策は、JSONデータを作るときにDatetimetime.Time型ではなくstring型にして、事前に希望の文字列を作っておくこと。

package main

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

func main() {
	// 時間のフィールドを持つ構造体
	type TestJson struct {
		Datetime string `json:"datetime"`
	}

	// 時刻の精度をミリ秒にする
	tj := TestJson{
		Datetime: time.Now().Format("2006-01-02T15:04:05.000Z07:00"),
	}
	j, _ := json.Marshal(&tj)

	fmt.Println(string(j))
}

これなら時刻がミリ秒の精度で生成されるのでPHPでもちゃんと解析できます。

対策その2

時間をいろいろな場所で使う場合はいちいちこんなことするのが面倒なので、ミリ秒精度で時刻をMarshalする構造体を作ってやればOKです。

package main

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

// ミリ秒精度でJSON出力するやつ
type MilliTime struct {
	time.Time
}

// 現在時刻から生成
func Now() MilliTime {
	return MilliTime{
		time.Now(),
	}
}

// JSON文字列を生成
func (m *MilliTime) MarshalJSON() ([]byte, error) {
	s := m.Format(`"2006-01-02T15:04:05.000Z07:00"`)
	return []byte(s), nil
}

func main() {
	// 時間のフィールドを持つ構造体
	type TestJson struct {
		Datetime MilliTime `json:"datetime"`
	}

	// ミリ秒精度で出力される
	tj := TestJson{
		Datetime: Now(),
	}
	j, _ := json.Marshal(&tj)

	fmt.Println(string(j))
}

このMilliTimeオブジェクトは、普通のtime.Timeオブジェクトで使えるメソッドをすべて使えます。

あとは必要に応じて、time.TimeからMilliTimeを生成するFromTime()、逆にtime.Timeを返すToTime()、他にもMarshalBinary()とかMarshalText()とかを作ってやればOK。

Unmarshalの精度は高くて困ることはないので、わざわざ実装しなくてもいいかもしれません。

それにしてもよぉ、既存の言語との互換性くらい考えてくれよぉGoさんよぉ。

1 件のコメント:

  1. Hard Rock Casino's Sacramento Casino - Slots fun88 fun88 10bet 10bet 카지노 카지노 63NFL Expert Picks Against the Spread Week 14 - Palmer

    返信削除