Пришел день, и конфигурационные файлы для нашего приложения стали настолько большими, что менеджеры намекнули что в JSON-конфигах получается подозрительно много фигурных и не фигурных скобочек, и им хотелось бы от них избавиться. Был дан тонкий намек, что неплохо бы приглядеться к YAML, ведь ходят слухи что он очень человекочитаемый. И скобочек никаких там нет. И списки красивые. Не внять старшим мы естественно не могли, вынуждены были изучать вопрос, искать разницу, плюсы и минусы обоих форматов. Очевидно, что такие сравнения затеваются лишь для того, чтобы подтвердить мнение руководителей или даже если не подтвердить, то они найдут почему они правы и почему стоит делать изменения :)
Уверен, что многие с данными форматами знакомы, но все же приведу краткое описание с википедии:
JSON (англ. JavaScript Object Notation) — текстовый формат обмена данными, основанный на JavaScript и обычно используемый именно с этим языком. Как и многие другие текстовые форматы, JSON легко читается людьми. Несмотря на происхождение от JavaScript (точнее, от подмножества языка стандарта ECMA-262 1999 года), формат считается языконезависимым и может использоваться практически с любым языком программирования. Для многих языков существует готовый код для создания и обработки данных в формате JSON.
YAML — человекочитаемый формат сериализации данных, концептуально близкий к языкам разметки, но ориентированный на удобство ввода-вывода типичных структур данных многих языков программирования. Название YAML представляет собой рекурсивный акроним YAML Ain't Markup Language («YAML — не язык разметки»). В названии отражена история развития: на ранних этапах язык назывался Yet Another Markup Language («Ещё один язык разметки») и даже рассматривался как конкурент XML, но позже был переименован с целью акцентировать внимание на данных, а не на разметке документов.
И так, что нам нужно:
- сделать одинаковый сложный JSON и YAML
- определить параметры по каким будем сравнивать
- десиреализовать в JabaBeans около 30 раз
- сравнить результат по скорости
- сравнить читаемость файлов
- сравнить удобство работы с форматом
Очевидно, что писать собственные парсеры мы не будем, поэтому для начала выберем для каждого формата по уже существующему парсеру.
Для json будем использовать gson (от google), а для yaml — snakeyaml (от не-знаю-кого).
Как видим все просто, нужно только создать достаточно сложную модель, которая будет имитировать сложность конфиг-файлов, и написать модуль который будет тестировать yaml и json парсеры. Приступим.
Нужна модель примерно такой сложности: 20 атрибутов разных типов + 5 коллекций по 5-10 элементов + 5 вложенных объектов по 5-10 элементов и 5 коллекций.
Этот этап всего сравнения смело можно назвать самым нудным и неинтересным. Были созданы классы, с незвучными именами типа Model, Emdedded1, и т.д. Но мы не гонимся за читаемостью кода (как минимум в этой части), поэтому так и оставим.
"embedded2": {
"strel1": "el1",
"strel2": "el2",
"strel4": "el4",
"strel5": "el5",
"strel6": "el6",
"strel7": "el7",
"intel1": 1,
"intel2": 2,
"intel3": 3,
"list1": [
1,
2,
3,
4,
5
],
"list2": [
1,
2,
3,
4,
5,
6,
7
],
"list3": [
"1",
"2",
"3",
"4"
],
"list4": [
"1",
"2",
"3",
"4",
"5",
"6"
],
"map1": {
"3": 3,
"2": 2,
"1": 1
},
"map2": {
"1": "1",
"2": "2",
"3": "3"
}
}
embedded2:
intel1: 1
intel2: 2
intel3: 3
list1:
- 1
- 2
- 3
- 4
- 5
list2:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
list3:
- '1'
- '2'
- '3'
- '4'
list4:
- '1'
- '2'
- '3'
- '4'
- '5'
- '6'
map1:
'3': 3
'2': 2
'1': 1
map2:
1: '1'
2: '2'
3: '3'
strel1: el1
strel2: el2
strel4: el4
strel5: el5
strel6: el6
strel7: el7
Соглашусь, что человекочитаемость параметр достаточно субъективный. Но все таки, на мой взгяд, yaml немного более приятен взгляду и более интуитивно понятен.
Далее, для каждого парсера напишем небольшую обертку. С методами serialize() и deserialize().
public class BookYAMLParser implements Parser<Book> {
String filename;
public BookYAMLParser(String filename) {
this.filename = filename;
}
@Override
public void serialize(Book book) {
try {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options);
FileWriter writer = new FileWriter(filename);
yaml.dump(book, writer);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Book deserialize() {
try {
InputStream input = new FileInputStream(new File(filename));
Yaml yaml = new Yaml();
Book data = (Book) yaml.load(input);
input.close();
return data;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (YamlException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
String message = "Exception in file " + filename + ", ";
throw new Exception(message + e.getMessage());
}
return null;
}
}
public class BookJSONParser implements Parser<Book> {
String filename;
public BookJSONParser(String filename) {
this.filename = filename;
}
@Override
public void serialize(Book book) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();;
try {
FileWriter writer = new FileWriter(filename);
String json = gson.toJson(book);
writer.write(json);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Book deserialize() {
Gson gson = new Gson();
try {
BufferedReader br = new BufferedReader(
new FileReader(filename));
JsonReader jsonReader = new JsonReader(br);
Book book = gson.fromJson(jsonReader, Book.class);
return book;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Как мы видим, оба формата имеют поддержку в java. Но для json выбор намного шире, это бесспорно.
Парсеры гоотовы, теперь рассмотрим реализацию сравнения. Тут тоже все предельно просто и очевидно. Есть простой метод, который 30 раз десериализует объекты из файла. Если кому интересно — код под спойлером.
public static void main(String[] args) {
String jsonFilename = "file.json";
String yamlFilename = "file.yml";
BookJSONParser jsonParser = new BookJSONParser(jsonFilename);
jsonParser.serialize(new Book(new Author("name", "123-123-123"), 123, "dfsas"));
BookYAMLParser yamlParser = new BookYAMLParser(yamlFilename);
yamlParser.serialize(new Book(new Author("name", "123-123-123"), 123, "dfsas"));
//json deserialization
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < LOOPS; i++) {
Book e = jsonParser.deserialize();
}
stopWatch.stop();
System.out.println("json worked: " + stopWatch.getTime());
stopWatch.reset();
//yaml deserialization
stopWatch.start();
for (int i = 0; i < LOOPS; i++) {
Book e;
e = yamlParser.deserialize();
}
stopWatch.stop();
System.out.println("yaml worked: " + stopWatch.getTime());
}
В реультате получаем следующий результат:
json worked: 278
yaml worked: 669
Как видно, json файлы парсятся примерно в три раза быстрее. Но абсолютная разница не является критичной, в наших масштабах. Поэтому это не сильный плюс в пользу json.
Это происходит потому что json парсится «на лету», то есть считывается посимвольно и сразу сохраняется в объект. Получается объект формируется за один проход по файлу. На самом деле я не знаю как работает именно этот парсер, но в общем схема такая.
А yaml, в свою очередь, более размеренный. Этап обработки данных делится на 3 этапа. Сначала строится дерево объектов. Потом оно еще каким-то образом преобразовывается. И только после этого этапа конвертируется в нужные структуры данных.
Небольшая сравнительная таблица ("+" — явное превосходство, "-" — явное отставание, "+-" — нет явного преимущества):
Json | YAML | |
---|---|---|
скорость работы | + | - |
человекочитаемость | - | + |
поддержка в java | +- | +- |
Как это можно подытожить?
Тут все очевидно, если вам важна скорость — тогда json, если человекочитаемость — yaml. Нужно просто решить, что важнее. Для нас оказалось — второе.
На самом деле, тут можно привести еще множество различных доводов в пользу каждого из форматов, но я считаю, что самые важные все таки эти два пункта.
Далее, при работе с yaml мне пришлось столкнусть с не очень красивой обработкой исключений, особенно при синтаксических ошибках. Также, пришлось протестировать различные yaml библиотеки. Еще, в завершение нужно было написать какую-нибудь валидацию. Были опробованы валидацию при помощи схем (там приходилось выхывать руби гемы), и bean-валидация на основе jsr-303. Если вас интересует какая-либо из этих тем — буду рад ответить на вопросы.
Спасибо за внимание:)
P.S.
Уже под конец написания статьи наткнулся на следующее сравнение yaml и json:
www.csc.kth.se/utbildning/kth/kurser/DD143X/dkand11/Group2Mads/victor.hallberg.malin.eriksson.report.pdf
На самом деле, появилось ощущения, что я проделал уже сделанную работу, причем сделанную более основательно и подробно, но ведь главное — это полученный опыт :)
P.P.S.
Описание работы парсеров взял оттуда. Мои извинения если перевод недостаточно точен и понятен.
Автор: motiamotia