#mysql #go #unmarshalling #sqlx #gqlgen
#mysql #Вперед #разархивировать #sqlx #gqlgen
Вопрос:
Я использую Mysql 8
. Я также использую 99designs/gqlgen
для автоматического создания структур на основе схемы GraphQL. Я должен был повторно использовать те же структуры при сканировании ответов MySQL. И, кроме того, при создании прототипа я хочу, чтобы в моей таблице было несколько JSON. Таким образом, структура:
type CustomizedItemInput struct {
Sku string `json:"sku"`
Name string `json:"name"`
Skus []*CustomizedComponent `json:"skus"`
...
Поскольку хранение (предоставление Value()
) проще, мне удалось успешно сохранить Sku в БД как JSON верхнего уровня. Выглядит так:
[{"sku": "123", "position": "LEFT"}, {"sku": "456", "position": "RIGHT"}]
Теперь, как мне получить это значение из базы данных и обратно в массив указателей внутри структуры без особых усилий?
Конечно, в идеале это должно быть сделано без изменения базовой структуры, потому что она автоматически генерируется.
ОБНОВЛЕНИЕ: добавление отладочной информации. Мне нужно прочитать строку DB в CustomizedItemView, которая в основном отражает CustomizedItemInput сверху:
type CustomizedItemView struct {
Sku string `json:"sku"`
Name string `json:"name"`
Skus []*CustomizedComponentView `json:"skus"`
...
Конечно, когда я говорю «без суеты», я имею в виду, что строка DB легко извлекается в структуру. Я могу добавить map[string]interface{}{}
все навороты и получить значение. Но я хочу, чтобы он был аккуратным, например:
var storedCustItem = model.CustomizedItemView{}
err := udb.Get(amp;storedCustItem, database.SelectCustomizationQuery, userID, custItem.Sku, createdAt)
Ошибка, которую я получаю, такова:
2020/10/10 20:38:24 sql: Scan error on column index 8, name "skus": unsupported Scan, storing driver.Value type []uint8 into type *[]*model.CustomizedComponentView
(8, потому что я удалил некоторые поля для примера).
Основная проблема заключается в том, что я не могу создать Scan()
для неназванного типа. Я создал оболочки для Value()
, потому что мои вставки более подробные, и я выполняю преобразование типов с типом оболочки в них:
type CustomizedComponentsIn []*CustomizedComponent
...
func (customizedComponents CustomizedComponentsIn) Value() (driver.Value, error)
...
tx.MustExec(database.SaveCustomizationCommand,
custItem.Sku,
custItem.Name,
model.CustomizedComponentsIn(custItem.Skus)
...
, что нормально для вставок, потому что будут некоторые значения, которые не принадлежат входной структуре.
Но я надеялся, по крайней мере, автоматически отсканировать значение в структуре представления.
Комментарии:
1. Вы можете либо изменить тип
Skus
поля на именованный тип среза, затем использовать этот именованный тип среза для реализацииsql.Scanner
интерфейса. Если вы не можете изменить типSkus
поля, вы можете написать тип оболочки, который реализует интерфейс или функцию преобразования, и вызвать эту функцию преобразования или использовать этот тип оболочки всякий раз, когда вы сканируетеSkus
поле, что требует, чтобы вы явно перечисляли поля при сканировании строк.2. @mkopriva, я добавил больше деталей к вопросу. Можете ли вы взглянуть? Я относительно новичок в go, поэтому я не уверен, что понимаю, что вы подразумеваете под оболочкой. Я вижу пример из вашего ответа, но это подразумевает изменение сгенерированной структуры.
3. Я обновил свой ответ, дайте мне знать, если это сделает его более понятным.
Ответ №1:
Если вы можете изменить тип Skus
поля, общим подходом было бы объявить тип среза, который реализует интерфейсы sql.Scanner
и driver.Valuer
, и использовать его вместо неназванного []*CustomizedComponent
типа.
Например:
type CustomizedItemInput struct {
Sku string `json:"sku"`
Name string `json:"name"`
Skus CustomizedComponentSlice `json:"skus"`
// ...
}
type CustomizedComponentSlice []*CustomizedComponent
// Value implements driver.Valuer interface.
func (s CustomizedComponentSlice) Value() (driver.Value, error) {
return json.Marshal(s)
}
// Scan implements sql.Scanner interface.
func (s *CustomizedComponentSlice) Scan(src interface{}) error {
var data []byte
switch v := src.(type) {
case string:
data = []byte(v)
case []byte:
data = v
default:
return nil
}
return json.Unmarshal(data, s)
}
Если вы не можете изменить тип Skus
поля, вам придется явно преобразовать поле во время сканирования.
Например, учитывая указанный выше именованный тип среза, вы могли бы сделать что-то вроде этого:
v := new(CustomizedItemView)
row := db.QueryRow("SELECT sku, name, skus FROM customized_item_view WHERE sku = ? LIMIT 1", sku)
err := row.Scan(
amp;v.Sku,
amp;v.Name,
// do the conversion here, and any other place where you're scanning Skus...
(*CustomizedComponentSlice)(amp;v.Skus),
)
if err != nil {
return err
}
fmt.Println(v.Skus) // result