Golang общая база данных одной записи в JSON - PullRequest
0 голосов
/ 14 декабря 2018

Я хочу получить записи из базы данных и упаковать их в json.У меня есть около 30 разных таблиц, поэтому я хочу универсальные функции, которые будут работать со всеми и любой из этих таблиц.Я использую xorm для доступа к базе данных.

Мне удалось создать СУХИЕ функции, которые извлекают данные, главным образом благодаря этому вопрос и ответ

Это работает, может упорядочить всезаписи в json:

type user struct {
   Id   int64  `json:"id"`
   Name string `json:"name"`
}
// type post
// etc.

type tableRecord struct {
    PrimaryKey string
    Data       interface{}
}

var ListOfTables = map[string]tableRecord{
    "users":{"id", &[]user{}},  // type user is struct for xorm with json annotation
    //"posts":{"post_id", &[]post{}},
    // etc.. 
}

for tableName, rec := range ListOfTables {
    err := xorm.Find(rec.Data)
    if err != nil {
        log.Print(err)
    }

    out, err := json.Marshal(rec.Data)
    if err != nil {
        log.Print(err)
    }
    log.Print(string(out)) // this yields json array
}

Однако я борюсь со способностью собрать одну запись в json.Я искал способы перебора интерфейса {}, который содержит фрагмент, нашел этот и подобные темы.Попытка:

switch reflect.TypeOf(reflect.ValueOf(rec.Data).Elem().Interface()).Kind() {
case reflect.Slice:
    s := reflect.ValueOf(reflect.ValueOf(rec.Data).Elem().Interface())
    for i := 0; i < s.Len(); i++ {
        entry := s.Index(i)
        log.Printf("%v\n", entry) // prints {1 John Doe}
        // log.Print(reflect.ValueOf(entry))
        data, err := json.MarshalIndent(entry, " ", "  ")
        if err != nil {
            log.Print(err)
        }
        log.Println(string(data)) // prints {} empty
    }
}  

Конечно, если я укажу, что rec.Data - это *[]user, это работает, но тогда мне придется переписать такой код для каждой таблицы, а это не то, что мне нужно.

switch t := rec.Data.(type) {
case *[]user:
    for _, entry := range *t {
        // log.Printf("loop %v", entry)
        data, err := json.MarshalIndent(entry, " ", "  ")
        if err != nil {
            log.Print(err)
        }
        log.Println(string(data)) // yields needed json for single record
    }
}

Или, может быть, существует совершенно другой, лучший подход к решению этой проблемы - любая запись базы данных в json.

ОБНОВЛЕНИЕ Проблема в том, что Xormожидает структуру?Я должен буду прочитать возможности и ограничения xorm.

slice := record.Slice()
log.Print(reflect.TypeOf(slice))

err = env.hxo.In(record.PrimaryKey(), insertIds).Find(slice) // or &slice
if err != nil {
    log.Print(err) // Table not found
}

// this works
var slice2 []*user
err = env.hxo.In(record.PrimaryKey(), insertIds).Find(&slice2)
if err != nil {
    log.Print(err) // 
}

1 Ответ

0 голосов
/ 14 декабря 2018

Итак, как я уже упоминал в комментарии, проще всего, если вы хотите иметь возможность получить один элемент из поля tableRecord.Data, было бы изменить тип поля на то, чем оно является на самом деле:

type tableRecord struct {
    PrimaryKey string
    Data       []interface{} // slice of whatever
}

Таким образом, вы можете написать что-то очень общее:

for tbl, record := range records {
    fmt.Printf("First record from table %s\n", tbl)
    b, _ := json.MarshalIndent(record[0], " ", "  ")
    fmt.Println(string(b))
    fmt.Prinln("other records...")
    b, _ = json.MarshalIndend(record[1:], " ", "  ")
    fmt.Println(string(b))
}

Однако, я бы подумал о том, чтобы реализовать интерфейс в моих типах БД.Что-то вроде:

type DBType interface {
    PrimaryKey() string
    TableName() string // xorm can use this to get the table name
    Slice() []DBType // can return []user or whatever
}

Так что вам больше не нужен тип tableRecord, и вы можете просто использовать переменную, подобную этой:

listOfTables := []DBType{user{}, ...}
for _, tbl := range listOfTables {
    data := tbl.Slice()
    // find data here
    fmt.Printf("First record from table %s\n", tbl.TableName())
    b, _ := json.MarshalIndent(data[0], " ", "  ")
    fmt.Println(string(b))
    fmt.Prinln("other records...")
    b, _ = json.MarshalIndend(data[1:], " ", "  ")
    fmt.Println(string(b))
}

Так что TL; DR того, чего не хватало в моем ответе / комментариях:

  • Приведение типа []user{} (или []DBTable) к []interface{} не работает, видякак вы не можете привести все элементы в срез в одном выражении.Вам нужно будет создать второй фрагмент типа []interface{} и скопировать значения следующим образом:

    slice: = userVar.Slice () data: = make ([] interface {}, len (slice))) для i: = диапазон slice {data [i] = slice [i] // копировать тип в интерфейс {} slice} вернуть tableRecord {userVar.PrimaryKey (), data}

Я создал небольшой рабочий пример того, как вы можете использовать интерфейсы, как описано выше.

DEMO

Чтобы избежать слишком большого количества беспорядка, вы можете изменить функцию Slice, чтобы вернуть []interface{} сразу же:

func(v T) Slice() []interface{
    return []interface{
        &T{},
    }
}

Что было не так с вашей реализацией Slice, так это то, что у вас было что-то вроде этого:

func (u *user) Slice() []DBTable {
    u = &user{} // you're re-assigning the receiver, losing all state!
    return []DBTable{u}
}

Получатель имеет тип указателя, поэтому любые переназначения, которые вы делаете, будут влиять на переменнуюна котором был назван func.Это не очень хорошая идея.Просто используйте получатели значений или, если вы хотите быть уверены, что интерфейс реализован только для переменных-указателей (обычная уловка, используемая, например, gRPC), заключается в реализации функции следующим образом:

func(*user) Slice() []DBTable{
    return []DBTable{&user{}}
}

Хороший пример этого трюка можно найти в сгенерированных pb.go файлах при использовании буферов протокола.Типы сообщений будут иметь такую ​​функцию:

func(*MsgType) ProtoMessage() {}
...