Неупорядочить массив json в массив структуры

Я использую Mysql 8. Я также использую 99designs/gqlgenдля автоматического создания структур на основе схемы GraphQL. Я должен был повторно использовать те же структуры при сканировании ответов MySql. И вдобавок ко всему, во время создания прототипа я хочу, чтобы в моей таблице было несколько JSON. Итак, структура такая:

type CustomizedItemInput struct {
    Sku              string                 `json:"sku"`
    Name             string                 `json:"name"`
    Skus             []*CustomizedComponent `json:"skus"`
    ...

С момента хранения (предоставления Value()) проще мне удалось сохранить Skus в БД как JSON верхнего уровня. Выглядит так:

[{"sku": "123", "position": "LEFT"}, {"sku": "456", "position": "RIGHT"}]

Теперь, как мне без особых усилий получить это значение из БД и обратно в массив указателей внутри структуры? Конечно, в идеале это должно быть сделано без изменения базовой структуры, поскольку она создается автоматически.

ОБНОВЛЕНИЕ: добавление отладочной информации. Мне нужно прочитать строку БД в CustomizedItemView, которая в основном отражает CustomizedItemInput сверху:

type CustomizedItemView struct {
    Sku              string                     `json:"sku"`
    Name             string                     `json:"name"`
    Skus             []*CustomizedComponentView `json:"skus"`
    ...

Конечно, когда я говорю "без суеты", я имею в виду беспрепятственное извлечение строки БД в структуру. Я могу добавить map[string]interface{}{}со всеми наворотами и получите значение. Но я хочу, чтобы он был аккуратным, например:

    var storedCustItem = model.CustomizedItemView{}
    err := udb.Get(&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 и 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 field вам нужно будет явно преобразовать поле во время сканирования.

Например, для указанного выше типа среза вы можете сделать что-то вроде этого:

v := new(CustomizedItemView)
row := db.QueryRow("SELECT sku, name, skus FROM customized_item_view WHERE sku = ? LIMIT 1", sku)
err := row.Scan(
    &v.Sku,
    &v.Name,
    // do the conversion here, and any other place where you're scanning Skus... 
    (*CustomizedComponentSlice)(&v.Skus),
)
if err != nil {
   return err
}
fmt.Println(v.Skus) // result
Другие вопросы по тегам