【Go】nullを許容するDBカラムに対するdate型の値の扱い
はじめに
DartからGolangのAPIにPOSTする場合、nullを許容するDBカラムに対するdate型の値をどのように扱うかで、ハマったので備忘録的にまとめ。
NULLを許可しない場合は、単純にtime.Time型として定義
前提としてデータベースのカラムがNULLを許容しない場合、Golangの構造体のフィールドを単純に time.Time
型として定義し、Dart側からは DateTime
オブジェクトを toString()
メソッド(または toIso8601String()
を使用してISO 8601形式で)で文字列化して送信するだけでよい。
GolangがISO 8601形式の文字列を time.Time
オブジェクトに解析できるのは、Golangの time
パッケージが内部的にこのフォーマットを理解し、正しくパース(解析)する機能を提供しているから。特に time.Parse
関数を用いると、指定された日付・時刻形式の文字列を time.Time
型の値に変換することができる。
HTTP通信を行う場合、データはテキスト形式でなければならず、DateTime
オブジェクトをそのまま送ることはできない。
Dartの DateTime
オブジェクトのデフォルトの toString()
メソッドを使用してデータを送ると、得られる文字列は通常 DateTime
オブジェクトが持つ完全な日付と時刻の情報を含むが、この形式はISO 8601形式と異なることがある。例えば、toString()
はタイムゾーンの情報を含む場合と含まない場合があり、そのフォーマットが一貫していないことがある。そのため、この方法でデータを送信すると、Golang側でのデータ受信・解析時に問題が発生する可能性がある。 toIso8601String()
を使用することが推奨される。
type RequestBody struct {
Date time.Time `json:"date"` // NULLを許容しない場合
}
import 'package:http/http.dart' as http;
void postData(DateTime date) async {
final response = await http.post(
Uri.parse('http://example.com/api/post'),
headers: {
'Content-Type': 'application/json'
},
body: jsonEncode({
'date': date.toIso8601String(), // DateTimeをISO 8601形式の文字列に変換
}),
);
if (response.statusCode == 200) {
print("Data posted successfully");
} else {
print("Failed to post data");
}
}
さて、nullを許容するDBカラムに対するdate型の値を、golangで処理する場合の方法を3つ紹介。
ポインタでnullを許可する
特徴:
- Dart側ではnull許容のDateTime?型を使用し、Golang側ではポインタ型の*time.Timeを使用する。
- Dart側でnullの場合は、Golang側でポインタがnilになる。
- ポインタがnilかどうかでnullを判定できるので、Dart側でnullを判定するための条件分岐が不要。空文字をDBに格納したくない場合には、空文字列をnull値としてDBに格納するか、空文字列を無視してDBに格納しないように設定するなど、別途処理が必要(から文字は有効なメモリアドレスを指しているとみなされるため)
import 'package:http/http.dart' as http;
DateTime? date; // null許容のDateTime型
// APIにPOSTする処理
void postData() async {
final response = await http.post(
Uri.parse('http://example.com/api/post'),
body: {'date': date?.toIso8601String()}, // DateTime型をISO 8601形式の文字列に変換して送信
);
}
package main
import (
"fmt"
"net/http"
"time"
)
type RequestBody struct {
Date *time.Time `json:"date,omitempty"` // ポインタ型でnullを許可
}
func handlePost(w http.ResponseWriter, r *http.Request) {
var requestBody RequestBody
// リクエストボディをパースしてRequestBody構造体に格納
// ...
if requestBody.Date != nil {
fmt.Println("Received date:", requestBody.Date)
} else {
fmt.Println("Received null date")
}
}
func main() {
http.HandleFunc("/api/post", handlePost)
http.ListenAndServe(":8080", nil)
}
sql.NullTimeを使う
Dartから送信する際に、nullの場合はnullで送信し、有効な日付がある場合は時間型の値を送信する。Golang側では、sql.NullTime型を使用して、データを受け取る。
特徴:
- Golang側でsql.NullTime型を使用して、nullを許容する時間型を表現する
- Golang側でValidフィールドを介してnullを判定する。
- SQLパッケージをインポートする必要がある。
- nullを判定するために、Validフィールドを介して条件分岐が必要。Validがfalseの場合にnullであることを示す。
import 'package:http/http.dart' as http;
DateTime? date; // null許容のDateTime型
// APIにPOSTする処理
void postData() async {
final response = await http.post(
Uri.parse('http://example.com/api/post'),
body: {'date': date != null ? date.toIso8601String() : null}, // nullの場合はnullを送信
);
}
package main
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"time"
)
type RequestBody struct {
Date sql.NullTime `json:"date"` // sql.NullTime型を使用
}
func handlePost(w http.ResponseWriter, r *http.Request) {
var requestBody RequestBody
// リクエストボディをパースしてRequestBody構造体に格納
// ...
if requestBody.Date.Valid {
fmt.Println("Received date:", requestBody.Date.Time)
} else {
fmt.Println("Received null date")
}
}
func main() {
http.HandleFunc("/api/post", handlePost)
http.ListenAndServe(":8080", nil)
}
sql.NullTime
は、JSONにシリアライズすると辞書型になる
sql.NullTime
は、Time
が Valid
かどうかも保持するため、JSONにシリアライズすると、Time
および Valid
のフィールドを含むオブジェクトになる。そのため、Dart側でこのオブジェクトを単に文字列として扱おうとすると下記エラーが発生する。
Golangのサーバーサイドから、、、
続きは、こちらで記載しています。
Discussion