Распаковка сущностей из нескольких массивов JSON без использования отражающего или дублирующего кода - PullRequest
0 голосов
/ 26 августа 2018

Я создаю клиент-оболочку JSON API, который должен получать постраничные результаты, где URL-адрес следующей страницы предоставляется предыдущей страницей.Чтобы уменьшить дублирование кода для 100+ объектов, которые используют один и тот же формат ответа, я хотел бы иметь один клиентский метод, который извлекает и отменяет маршализацию различных объектов со всех страниц, разбитых на страницы.

Мой текущий подход в упрощенном виде (псевдо) версия (без ошибок и т. д.):

type ListResponse struct {
    Data struct {
        Results []interface{} `json:"results"`
        Next    string        `json:"__next"`
    } `json:"d"`
}

func (c *Client) ListRequest(uri string) listResponse ListResponse {
    // Do a http request to uri and get the body
    body := []byte(`{ "d": { "__next": "URL", "results": []}}`)
    json.NewDecoder(body).Decode(&listResponse)
}

func (c *Client) ListRequestAll(uri string, v interface{}) {
    a := []interface{}
    f := c.ListRequest(uri)
    a = append(a, f.Data.Results...)

    var next = f.Data.Next
    for next != "" {
        r := c.ListRequest(next)
        a = append(a, r.Data.Results...)
        next = r.Data.Next
    }

    b, _ := json.Marshal(a)
    json.Unmarshal(b, v)
}

// Then in a method requesting all results for a single entity
var entities []Entity1
client.ListRequestAll("https://foo.bar/entities1.json", &entities)

// and somewehere else
var entities []Entity2
client.ListRequestAll("https://foo.bar/entities2.json", &entities)

Проблема, однако, заключается в том, что этот подход неэффективен и использует слишком много памяти и т. д., т. е. сначала происходит отмена сортировки в общем ListResponse с результатами как []interface{} (чтобы увидеть следующий URL-адрес и объединить результаты в один фрагмент), а затем маршалировать []interface{} для отмены маршалинга его прямо в направлении целевого фрагмента []Entity1.

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

1 Ответ

0 голосов
/ 30 августа 2018

Посмотрите на тип RawMessage в пакете encoding/json. Это позволяет отложить декодирование значений JSON на потом. Например:

Results []json.RawMessage `json:"results"`

или даже ...

Results json.RawMessage `json:"results"`

Поскольку json.RawMessage - это просто кусочек байтов, это будет намного эффективнее, чем промежуточный []interface{}, который вы отменяете.

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

// Then in a method requesting all results for a single entity
var entityPages [][]Entity1
client.ListRequestAll("https://foo.bar/entities1.json", &entityPages)

Это все еще имеет проблему неограниченного потребления памяти, которую имеет ваш общий дизайн, так как вам нужно загрузить все страницы / элементы одновременно. Возможно, вы захотите перейти на абстракцию Open / Read, например, работать с файлами. У вас есть какой-нибудь метод Open, который возвращает другой тип, который, например, os.File, предоставляет метод для чтения подмножества данных за один раз, при этом внутренний запрос страниц и буферизация по мере необходимости.

Возможно, что-то вроде этого (не проверено):

type PagedReader struct {
  c *Client

  buffer []json.RawMessage

  next string
}

func (r *PagedReader) getPage() {
  f := r.c.ListRequest(r.next)
  r.next = f.Data.Next
  r.buffer = append(r.buffer, f.Data.Results...)
}

func (r *PagedReader) ReadItems(output []interface{}) int {
  for len(output) > len(buffer) && r.next != "" {
    r.getPage()
  }

  n := 0
  for i:=0;i<len(output)&&i< len(r.buffer);i++ {
    json.Unmarshal(r.buffer[i], output[i] )
    n++
  }
  r.buffer = r.buffer[n:]
  return n
}
...