方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

[Go] Handling Date type values ​​for nullable DB columns

This article can be read in about 15 minutes.

Introduction

When posting from Dart to Golang’s API, I got stuck on how to handle date-type values ​​for nullable DB columns, so I summarized it as a memorandum.

If you don’t allow NULLs, simply define it as time.Time type

If the database column does not allow NULLs, simply time.Timedefine the field of the Golang structure as a type, and from the Dart side, convert DateTimethe object into a string using toString()a method (or toIso8601String()in ISO 8601 format using ) and send it. Just do it.

Golang can parse ISO 8601 format strings into objects because Golang packages internally understand this format and provide the functionality to parse it correctly. In particular, functions can be used to convert a specified date/time format string into a type value.time.Timetimetime.Parsetime.Time

When performing HTTP communication, data must be in text format, and DateTimeobjects cannot be sent as is.

When you send data using Dart’s DateTimedefault methods for objects, the resulting string typically contains the object’s complete date and time information, but this format may differ from the ISO 8601 format. For example, may or may not include time zone information, and its format may be inconsistent. Therefore, if you send data using this method, problems may occur when Golang receives and analyzes the data. It is recommended to use.toString()DateTimetoString()toIso8601String()

Go

Go
type RequestBody struct {
    Date time.Time `json:"date"` // NULLを許容しない場合
}

Dart

Dart
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");
  }
}

Now, I will introduce three ways to use Golang to process date-type values ​​for DB columns that allow nulls.

Allow nulls in pointers

Features :

  • On the Dart side, use the nullable DateTime? type, and on the Golang side, use the pointer type *time.Time.
  • If it is null on the Dart side, the pointer will be nil on the Golang side.
  • Null can be determined based on whether the pointer is nil, so there is no need for a conditional branch to determine null on the Dart side. If you do not want to store empty strings in the DB, separate processing is required, such as storing empty strings as null values ​​in the DB or configuring settings to ignore empty strings and not store them in the DB (characters from (as it is assumed that it points to a memory address)

Dart

Dart
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形式の文字列に変換して送信
  );
}

Go

Go
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)
}

Use sql.NullTime

When sending from Dart, if it is null, it will be sent as null, and if there is a valid date, it will be sent as a time type value. On the Golang side, use the sql.NullTime type to receive data.

Features :

  • Use sql.NullTime type on Golang side to represent nullable time type
  • Determine null through the Valid field on the Golang side.
  • You need to import the SQL package.
  • Conditional branching is required via the Valid field to determine null. Indicates null when Valid is false.

Dart

Dart
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を送信
  );
}

Go

Go
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 becomes a dictionary type when serialized to JSON

sql.NullTimeand also holds Timewhether becomes an object containing fields of and. Therefore, if you try to simply treat this object as a string on the Dart side, the following error will occur.ValidTimeValid

sql.NullTimeJSON response ( fields) sent from the Golang server side

JSON

JSON
{
  "date": {
    "Time": "2024-05-06T14:00:00Z",
    "Valid": true
  }
}

ShellScript

ShellScript
flutter: type '_Map<String, dynamic>' is not a subtype of type 'String' in type cast

In order to properly process this data on the Dart/Flutter side, it is necessary to refer to a specific key in the map and extract the value instead of directly casting it to a string from the map.

Dart

Dart
var response = await api.getUser();
var dateMap = response['date'] as Map<String, dynamic>;
if (dateMap['Valid'] == true) {
  var dateString = dateMap['Time'] as String;  // 正しい日付の文字列を取得
}

Use []uint8

Features :

  • Used to represent binary data or string data.
  • Even if it is null, the value is sent as an empty string (or an empty byte slice), so a conditional branch is required on the Golang side to distinguish between an empty string and a null.

Byte slice → string → time.Time object

Convert byte slice to string :

  • requestBody.Dateis a byte slice of type and converts []uint8it to a string. string(requestBody.Date)As a result, the string data originally sent from Dart (for example, a format like “2024-05-01T12:00:00Z”) is converted back to string format.

Parse string date data time.Timeinto an object :

  • Next, convert time.Parse(time.RFC3339, string(requestBody.Date))the date data in string format to a Go object using. defines a date and time format, and strings that follow this format can be converted to objects appropriately.time.Timetime.RFC3339time.Time

Store in database :

  • Finally, time.Timeuse this object to store date data in a TIMESTAMP-type column in the database. This ensures that time information is correctly stored in the database and allows query operations related to date and time.

Dart

Dart
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を送信
  );
}

Go

Go
package main

import (
    "fmt"
    "net/http"
    "time"
)

type RequestBody struct {
    Date []uint8 `json:"date"` // []uint8型を使用
}

func handlePost(w http.ResponseWriter, r *http.Request) {
    var requestBody RequestBody
    // リクエストボディをパースしてRequestBody構造体に格納
    // ...

    if len(requestBody.Date) > 0 {
        date, err := time.Parse(time.RFC3339, string(requestBody.Date))
        if err != nil {
            fmt.Println("Error parsing date:", err)
            return
        }
        fmt.Println("Received date:", date)
    } else {
        fmt.Println("Received null date")
    }
}

func main() {
    http.HandleFunc("/api/post", handlePost)
    http.ListenAndServe(":8080", nil)
}

コメント

Copied title and URL