Type Assertion and Struct Handling in Go
Go's type assertion mechanism provides a powerful way to work with interface values and extract concrete types. This article explores practical applications through a search and data retrieval example combining Elasticsearch and MySQL.
package main
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"log"
"strconv"
"strings"
"github.com/elastic/go-elasticsearch/v7"
_ "github.com/go-sql-driver/mysql"
)
type ChapterMetadata struct {
ChapterID int `json:"chapter_id"`
Name string `json:"name"`
}
type BookInformation struct {
ChapterID int
Name string
Writer string
Description string
ReleaseDate string
}
func executeSearch() {
client, err := elasticsearch.NewDefaultClient()
if err != nil {
log.Fatalf("Client initialization failed: %s", err)
}
targetName := "狼性总裁"
searchQuery := fmt.Sprintf(`{
"query": {
"match": {
"title": "%s"
}
}
}`, targetName)
result, err := client.Search(
client.Search.WithContext(context.Background()),
client.Search.WithIndex("book_chapters_index"),
client.Search.WithBody(strings.NewReader(searchQuery)),
client.Search.WithTrackTotalHits(true),
)
if err != nil {
log.Fatalf("Search execution error: %s", err)
}
defer result.Body.Close()
var responseData map[string]interface{}
if err := json.NewDecoder(result.Body).Decode(&responseData); err != nil {
log.Fatalf("Response parsing error: %s", err)
}
hitList := responseData["hits"].(map[string]interface{})["hits"].([]interface{})
var chapterIDs []int
for _, item := range hitList {
sourceData := item.(map[string]interface{})["_source"].(map[string]interface{})
chapterID := int(sourceData["id"].(float64))
chapterIDs = append(chapterIDs, chapterID)
}
if len(chapterIDs) == 0 {
fmt.Println("No matching chapters found")
return
}
fmt.Printf("Located chapter IDs: %v\n", chapterIDs)
connectionString := "user:pass@tcp(localhost:3306)/library"
database, err := sql.Open("mysql", connectionString)
if err != nil {
log.Fatalf("Database connection failed: %s", err)
}
defer database.Close()
idStrings := make([]string, len(chapterIDs))
for i, id := range chapterIDs {
idStrings[i] = strconv.Itoa(id)
}
idClause := strings.Join(idStrings, ",")
dbQuery := fmt.Sprintf(
"SELECT chapter_id, name, writer, description, release_date FROM book_chapters WHERE chapter_id IN (%s)",
idClause,
)
rows, err := database.Query(dbQuery)
if err != nil {
log.Fatalf("Query execution error: %s", err)
}
defer rows.Close()
var books []BookInformation
for rows.Next() {
var book BookInformation
if err := rows.Scan(&book.ChapterID, &book.Name, &book.Writer, &book.Description, &book.ReleaseDate); err != nil {
log.Fatalf("Row scanning error: %s", err)
}
books = append(books, book)
}
for _, book := range books {
fmt.Printf("ID: %d, Title: %s, Author: %s, Summary: %s, Date: %s\n",
book.ChapterID, book.Name, book.Writer, book.Description, book.ReleaseDate)
}
}
func main() {
executeSearch()
}
The example demonstrates multiple type assertion pattterns:
- Extracting map data from interface{} resposnes
- Convreting float64 to int for ID values
- Handling slice interfaces from external APIs
Type assertions are performed using the .(desiredType) syntax, which panics if the assertion fails. For production code, consider using the comma-ok variant:
if sourceMap, ok := item.(map[string]interface{}); ok {
// Safe to use sourceMap
}