Статьи

Разрешение изменений схемы JSON с помощью Drill и Python

Drill — это фантастический инструмент для запроса данных JSON. Но Drill не волшебный, и иногда он сталкивается с некоторыми данными, которые он не может обработать (пока). Этот пост посвящен примеру такого сценария и тому, как вы можете решить эту проблему, используя немного кода Python.

сценарий

У вас есть данные, где схема меняется. В этом примере мы рассмотрим случай, когда у вас есть поле, которое изменяется от одного значения к списку или наоборот.

Образец данных

Эти данные разбиты на два файла.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
  "name": "Vince",
  "favorite_foods": [
    {
      "name": "ice-cream",
      "flavors": "vanilla"
    },
    {
      "name": "cheeseburgers",
      "flavors": [
        "animal style",
        "black label"
      ]
    }
  ]
}
{
  "name": "Nikki",
  "favorite_foods": [
    {
      "name": "ice-cream",
      "flavors": [
        "chocolate",
        "dulce de leche"
      ]
    },
    {
      "name": "cheeseburgers",
      "flavors": [
        "black label"
      ]
    }
  ]
}

запрос

Оба файла являются действительными JSON, и это хорошо, но оказывается, что схема меняется. Винсу нравится только ванильное мороженое, и это хранится как единая ценность. Никки любит шоколад и Дульсе де Лече (Винс не отказывается от них, просто не его любимый), поэтому они хранятся в виде списка.

Drill предпочел бы, чтобы вы не организовывали свои данные таким образом, и не стесняется сказать вам следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;
java.lang.RuntimeException: java.sql.SQLException: DATA_READ ERROR: You tried to start when you are using a ValueWriter of type NullableVarCharWriterImpl.
 
File  /Users/vince/src/drill-data-prep-example/schema-change/data/vince.json
Record  1
Line  1
Column  127
Field  flavors
Fragment 0:0
 
[Error Id: 2c9020c1-cfcf-42e2-926c-d00962ceb7f9 on 192.168.56.1:31010]
    at sqlline.IncrementalRows.hasNext(IncrementalRows.java:73)
    at sqlline.TableOutputFormat$ResizingRowsProvider.next(TableOutputFormat.java:87)
    at sqlline.TableOutputFormat.print(TableOutputFormat.java:118)
    at sqlline.SqlLine.print(SqlLine.java:1583)
    at sqlline.Commands.execute(Commands.java:852)
    at sqlline.Commands.sql(Commands.java:751)
    at sqlline.SqlLine.dispatch(SqlLine.java:738)
    at sqlline.SqlLine.begin(SqlLine.java:612)
    at sqlline.SqlLine.start(SqlLine.java:366)
    at sqlline.SqlLine.main(SqlLine.java:259)

Что если мы изменим порядок этих объектов на входе? Нравится ли Drill лучше, когда мы начинаем со списка, а потом меняем одно значение?

01
02
03
04
05
06
07
08
09
10
11
0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;
Error: DATA_READ ERROR: You tried to write a VarChar type when you are using a ValueWriter of type SingleListWriter.
 
File  /Users/vince/src/mapr/drill-data-prep/schema-change/sample_data.json
Record  2
Line  3
Column  84
Field  flavors
Fragment 0:0
 
[Error Id: 93ceda84-8710-46de-8d5f-6b2290914703 on 192.168.56.1:31010] (state=,code=0)

Нет. Дрель все еще недовольна.

В Drill 1.6 может быть включен тип объединения (экспериментальный) . Это позволит хранить несколько типов данных в одном поле, что может помочь нам обойти эту проблему. Итак, давайте включим это и попробуем.

1
2
3
4
5
6
7
8
ALTER SESSION SET `exec.enable_union_type` = true;
0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;
Error: Unexpected RuntimeException: java.lang.IllegalArgumentException: The field $offsets$(UINT4:REQUIRED) doesn't match the provided metadata major_type {
  minor_type: MAP
  mode: REQUIRED
}
 
. (state=,code=0)

К сожалению, это тоже не работает. Итак, давайте отключим тип объединения и продолжим.

В чем проблема?

Drill не нравится, когда вы даете ему данные, в которых тип меняется с одного значения в список или наоборот.

Как нам обойти это?

Мы массируем данные таким образом, чтобы поля, в которых иногда, но не всегда, использовались списки, становились полями, где списки используются всегда. В примере данных проблема была flavors одним из полей flavors . Дрель услужливо определила строку и столбец, с которыми возникла проблема, так что вы можете просмотреть ее, прежде чем приступить к написанию кода для ее устранения.

Один из способов исправить данные в Python. Вы можете просто прочитать в объектах JSON и переписать их, преобразовав отдельные значения в списки с одним значением:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
import json
import types
 
for filename in ("data/vince.json", "data/nikki.json"):
    # Read the file into memory.
    with file(filename) as infile:
        data = json.loads(infile.read())
 
    # Open and truncate the input file.
    with file(filename, "w") as outfile:
        # enumerate returns an incrementing integer with each iteration.
        # convenient way to keep track of the index into the list
        # we need to modify.
        for i,f in enumerate(data["favorite_foods"]):
            # Check if the value pointed to by "flavors" is of type
            # ListType.
            if type(f["flavors"]) != types.ListType:
                # If it's not, then overwrite the value as a list.
                data["favorite_foods"][i]["flavors"] = [ f["flavors"] ]
        outfile.write(json.dumps(data))

Теперь, запрашивая обработанные данные, Drill более счастлив:

1
2
3
4
5
6
7
8
0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;
+---------------------------------------------------------------------------------------------------------------------+--------+
|                                                   favorite_foods                                                    |  name  |
+---------------------------------------------------------------------------------------------------------------------+--------+
| [{"flavors":["chocolate","dulce de leche"],"name":"ice-cream"},{"flavors":["black label"],"name":"cheeseburgers"}]  | Nikki  |
| [{"flavors":["vanilla"],"name":"ice-cream"},{"flavors":["animal style","black label"],"name":"cheeseburgers"}]      | Vince  |
+---------------------------------------------------------------------------------------------------------------------+--------+
2 rows selected (0.119 seconds)

Более счастливые запросы:

01
02
03
04
05
06
07
08
09
10
11
12
0: jdbc:drill:zk=local> select t.name, t.yum.name as fave_food, flatten(t.yum.flavors) as fave_flave from (select t.name, flatten(t.favorite_foods) as yum from (select name,favorite_foods from `/Users/vince/src/drill-data-prep-example/schema-change/data`) t) t;
+--------+----------------+-----------------+
|  name  |   fave_food    |   fave_flave    |
+--------+----------------+-----------------+
| Nikki  | ice-cream      | chocolate       |
| Nikki  | ice-cream      | dulce de leche  |
| Nikki  | cheeseburgers  | black label     |
| Vince  | ice-cream      | vanilla         |
| Vince  | cheeseburgers  | animal style    |
| Vince  | cheeseburgers  | black label     |
+--------+----------------+-----------------+
6 rows selected (0.153 seconds)

Интересная заметка

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

Ссылка: Устранение изменений схемы JSON с помощью Drill и Python от нашего партнера по JCG Винса Гонсалеса в блоге Mapr .