среда, 5 мая 2021 г.

Клиент Go для Elasticsearch: кодирование и декодирование полезной нагрузки JSON

Одна из тем, которую мы затронули лишь вкратце, - это работа с полезными нагрузками JSON. Клиент, как упоминалось в одном из предыдущих постов, представляет тело запроса и ответа как io.Reader, оставляя любое кодирование и декодирование вызывающему коду. Рассмотрим различные подходы, начиная с декодирования (десериализации) тела ответа.

Самый простой вариант - просто использовать пакет encoding/json из стандартной библиотеки для декодирования ответа в map[string]interface{} или настраиваемый тип структуры:

var r map[string]interface{}

res, _ := es.Search(es.Search.WithTrackTotalHits(true))
json.NewDecoder(res.Body).Decode(&r)

fmt.Printf(
  "[%s] %d hits; took: %dms\n",
  res.Status(),
  int(r["hits"].(map[string]interface{})["total"].(map[string]interface{})["value"].(float64)),
  int(r["took"].(float64)),
)

// => [200 OK] 1 hits; took: 10ms

Несмотря на простоту, этот вариант далеко не самый удобный или эффективный: обратите внимание, как вам нужно преобразовать каждую часть структуры, чтобы сделать значение полезным в вашем коде. Есть способы получше.

Если все, что вас интересует, это получение пары значений из ответа и их использование или отображение, привлекательным вариантом является использование пакета tidwall/gjson. Это позволяет вам использовать "точечную нотацию", чтобы легко и более эффективно "извлекать" значения из ответа:

var b bytes.Buffer

res, _ := es.Search(es.Search.WithTrackTotalHits(true))
b.ReadFrom(res.Body)

values := gjson.GetManyBytes(b.Bytes(), "hits.total.value", "took")
fmt.Printf(
  "[%s] %d hits; took: %dms\n",
  res.Status(),
  values[0].Int(),
  values[1].Int(),
)
// => [200 OK] 1 hits; took: 10ms

Еще один вариант, особенно для более сложной кодовой базы, - использовать такой пакет, как mailru/easyjson, который использует генерацию кода для эффективного кодирования и декодирования полезной нагрузки JSON в настраиваемые типы структур - соответствующий пример и связанную с ним папку модели.

Примечание. Запустите бенчмарки в своей собственной среде, чтобы сравнить производительность различных пакетов JSON.

Когда дело доходит до кодирования (сериализации) тела запроса, самый простой вариант - использовать тип, поддерживающий интерфейс io.Reader, например bytes.Buffer:

var b bytes.Buffer
b.WriteString(`{"title" : "`)
b.WriteString("Test")
b.WriteString(`"}`)

res, _ := es.Index("test", &b)
fmt.Println(res)
// => [201 Created] {"_index":"test","_id":"uFeRWXQBeb...

Поскольку структуры кодирования или значения map[string]interface{} встречаются очень часто, пакет esutil предоставляет помощник, который выполняет сериализацию и преобразование в io.Reader, поэтому эквивалент приведенного выше кода будет выглядеть следующим образом:

type MyDocument struct {
  Title string `json:"title"`
}

doc := MyDocument{Title: "Test"}

res, _ := es.Index("test", esutil.NewJSONReader(&doc))
fmt.Println(res)
// [201 Created] {"_index":"test","_id":"wleUWXQBe...

Примечание. Помощник хорошо работает с пользовательскими кодировщиками JSON. Если тип реализует интерфейс esutil.JSONEncoder, автоматически используется метод EncodeJSON(); в противном случае он возвращается к стандартной библиотеке.

Чтобы понять, как использовать клиент в обычном приложении, потратьте некоторое время на ознакомление с исчерпывающим примером xkcdsearch. Он индексирует информацию из JSON API и позволяет искать ее в командной строке и в браузере. Он демонстрирует несколько методов, таких как встраивание клиента в собственный тип, построение запросов, анализ ответов, выделение совпадающих фраз в результатах, имитация клиента для тестов и многое другое. Вы можете предварительно просмотреть приложение в Интернете.


Читайте также:


Комментариев нет:

Отправить комментарий