Здравствуйте, νsb, Вы писали:
νsb>Статью не читал, но парсинг JSON-а делается в пару сотен строк, нет там ничего сложного, формат простейший.
Формат чисел задан не общепринятым способом, так что один их парсинг займёт пару сотен строк.
И каждый день — без права на ошибку...
Re[5]: JSON vs BSON: очередное торжество больного воображения и кривых рук
Здравствуйте, B0FEE664, Вы писали:
νsb>>Статью не читал, но парсинг JSON-а делается в пару сотен строк, нет там ничего сложного, формат простейший. BFE>Формат чисел задан не общепринятым способом, так что один их парсинг займёт пару сотен строк.
Можно пример, который не парсится общепринятым способом?
Re[7]: JSON vs BSON: очередное торжество больного воображения и кривых рук
Здравствуйте, B0FEE664, Вы писали:
BFE> числа выделены в отдельный тип данных, но целые числа не отличаются от чисел с плавающей точкой,
1 — целое число, 1.0 — число с плавающей точкой.
BFE> зачем-то выделен отдельный тип boolean и загадочное значение null, которое можно было бы заменить отсутствием значения { "x":null } <=> { "x": }.
Здравствуйте, rudzuk, Вы писали:
BFE>> числа выделены в отдельный тип данных, но целые числа не отличаются от чисел с плавающей точкой, R>1 — целое число, 1.0 — число с плавающей точкой.
Логично, но в Json этого различия нет.
И каждый день — без права на ошибку...
Re[9]: JSON vs BSON: очередное торжество больного воображения и кривых рук
Здравствуйте, B0FEE664, Вы писали:
BFE> BFE>> числа выделены в отдельный тип данных, но целые числа не отличаются от чисел с плавающей точкой,
BFE> R>1 — целое число, 1.0 — число с плавающей точкой.
BFE> Логично, но в Json этого различия нет.
Здравствуйте, rudzuk, Вы писали:
BFE>> R>1 — целое число, 1.0 — число с плавающей точкой. BFE>> Логично, но в Json этого различия нет. R>Это в JS его нет, а JSON есть.
1E-2 — это целое или с плавающей точкой?
И каждый день — без права на ошибку...
Re[6]: JSON vs BSON: очередное торжество больного воображения и кривых рук
Здравствуйте, νsb, Вы писали:
νsb>>>Статью не читал, но парсинг JSON-а делается в пару сотен строк, нет там ничего сложного, формат простейший. BFE>>Формат чисел задан не общепринятым способом, так что один их парсинг займёт пару сотен строк. νsb>Можно пример, который не парсится общепринятым способом?
1. — парсится, а не должен 1.0e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 — рапарсится общепринятым способом?
И каждый день — без права на ошибку...
Re[11]: JSON vs BSON: очередное торжество больного воображения и кривых рук
Здравствуйте, B0FEE664, Вы писали:
BFE> BFE>> R>1 — целое число, 1.0 — число с плавающей точкой. BFE> BFE>> Логично, но в Json этого различия нет.
BFE> R>Это в JS его нет, а JSON есть.
BFE> 1E-2 — это целое или с плавающей точкой?
Экспоненциальная форма используется для записи вещественных чисел — значит, это число с плавающей точкой.
Здравствуйте, rudzuk, Вы писали:
BFE>> BFE>> R>1 — целое число, 1.0 — число с плавающей точкой. BFE>> BFE>> Логично, но в Json этого различия нет. BFE>> R>Это в JS его нет, а JSON есть. BFE>> 1E-2 — это целое или с плавающей точкой? R>Экспоненциальная форма используется для записи вещественных чисел — значит, это число с плавающей точкой.
Экспоненциальная форма может использоваться для записи целых чисел: 1E2 == 100
Я согласен, что формально можно разделить на целые и на вещественные, но вот только это никак не обозначено (видимо намеренно) в Json.
И каждый день — без права на ошибку...
Re[7]: JSON vs BSON: очередное торжество больного воображения и кривых рук
Здравствуйте, B0FEE664, Вы писали:
BFE> R>Экспоненциальная форма используется для записи вещественных чисел — значит, это число с плавающей точкой.
BFE> Экспоненциальная форма может использоваться для записи целых чисел: 1E2 == 100
Не, это вещественное число с нулевой дробной частью. У целых чисел нет понятия мантиссы.
BFE> Я согласен, что формально можно разделить на целые и на вещественные, но вот только это никак не обозначено (видимо намеренно) в Json.
Здравствуйте, rudzuk, Вы писали:
BFE>> R>Экспоненциальная форма используется для записи вещественных чисел — значит, это число с плавающей точкой. BFE>> Экспоненциальная форма может использоваться для записи целых чисел: 1E2 == 100 R>Не, это вещественное число с нулевой дробной частью. У целых чисел нет понятия мантиссы.
Вещественное число является целым, если его десятичное представление не содержит дробной части
отсюда
Ну это и понятно, так как целые — подмножество вещественных.
BFE>> Я согласен, что формально можно разделить на целые и на вещественные, но вот только это никак не обозначено (видимо намеренно) в Json. R>Разделение вполне очевидно, как по мне
Наверное всё же следует отличать вещественные от чисел с плавающей точкой...
Ну да не суть в данном вопросе.
Я о другом. Смотрите, если формат предполагает, например, запись числа в виде двух шестнадцатеричных цифр HH, то я знаю, что мне для парсинга хватит одного байта и при любом раскладе, всё, что выходит за эти два символа — ошибка. Здесь же, для разбора придётся на каждом шаге проверять переполнение числа, что заметно усложняет разбор. При этом парсер либо должен использовать максимально большой тип для результата, либо ему надо задавать пределы ожидаемого значения, до парсинга, что усложняет сам парсер.
Возьмём, число 9223372036854775809 — если это целое, то в 64 бита оно не поместится, но его можно с округлением записать хоть в float с одинарной точностью. И что делать парсеру? Выдавать ошибку или же конвертировать в float64 ?
Универсальный парсер этого знать не может. Это может знать только использующая парсер программа. Быть может это IP6 адрес, а может число с плавающей точкой, просто так получилось, что нет дробной части. Из самого формата никак не вывести, что это за значение.
И каждый день — без права на ошибку...
Re[7]: JSON vs BSON: очередное торжество больного воображени
Здравствуйте, B0FEE664, Вы писали:
νsb>>>>Статью не читал, но парсинг JSON-а делается в пару сотен строк, нет там ничего сложного, формат простейший. BFE>>>Формат чисел задан не общепринятым способом, так что один их парсинг займёт пару сотен строк. νsb>>Можно пример, который не парсится общепринятым способом?
BFE>1. — парсится, а не должен BFE>1.0e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 — рапарсится общепринятым способом?
В жаве Double.parseDouble оба значения парсит как положено. new BigDecimal, если хочется без ограничений — тоже парсит.
Не очень понял, почему парсер не должен принимать невалидный вход. Парсер должен работать на валидных входах. Как поступать с невалидным входом это уже вопрос философский. В конкретном данном случае парсить 1. без ошибки это скорей правильно, чем неправильно. Очень полная реализация, конечно, может включать и режим абсолютно строгого парсера. А я бы и комментарии парсил и строки без кавычек.
Вот при сериализации нужно генерировать строго валидный JSON, это безусловно.
Если не ошибаюсь, это старый юниксовый принцип — будь либерален в том, что принимаешь, будь строг в том, что отдаёшь.
Впрочем если очень хочется — конкретно это случай с точкой в конце отловить и отдельно обработать не проблема. Не думаю, что таких случаев будет очень много.
PS чтобы не балаболить, прям сейчас напишу строгий парсер жсона, посмотрим, получится ли.
Здравствуйте, B0FEE664, Вы писали:
BFE> Вещественное число является целым, если его десятичное представление не содержит дробной части
BFE> отсюда BFE> Ну это и понятно, так как целые — подмножество вещественных.
Терминологию мы оспаривать не будем (с). Все так, но в информатике разделение вполне конкретное и экспоненциальная запись для целых чисел не используется.
BFE> Я о другом. Смотрите, если формат предполагает, например, запись числа в виде двух шестнадцатеричных цифр HH, то я знаю, что мне для парсинга хватит одного байта и при любом раскладе, всё, что выходит за эти два символа — ошибка. Здесь же, для разбора придётся на каждом шаге проверять переполнение числа, что заметно усложняет разбор. При этом парсер либо должен использовать максимально большой тип для результата, либо ему надо задавать пределы ожидаемого значения, до парсинга, что усложняет сам парсер.
Ну да, в JSON не указан диапазон чисел, но мир вообще не идеален. Да, это усложняет парсер, но чего уж тут поделаешь.
BFE> Возьмём, число 9223372036854775809 — если это целое, то в 64 бита оно не поместится, но его можно с округлением записать хоть в float с одинарной точностью. И что делать парсеру? Выдавать ошибку или же конвертировать в float64 ?
Все довольно просто: пока не встречены явные признаки числа с плавающей точкой (экспоненциальная запись, дробная часть), число считается целым. Ну а там от возможностей парсера зависит, он может и не ограничиваться 64-битами, или же наоборот, может быть ограничен всего 32'я, а то и 16'ю.
BFE> ...а может число с плавающей точкой, просто так получилось, что нет дробной части.
Если число с плавающей точкой, то дробная часть должна присутствовать обязательно, даже если она нулевая. Это и есть та информация, которая позволит диферинцировать тип представленного числа.
Вроде написал, на простых тестах работает. Как раз где-то в два часа уложился. Старался всё в точности по спеке делать. Баги если и есть, то исправляемые и размер не увеличат. 229 строк. И форматтер еще строк на 50.
код
package test;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public sealed interface Json {
record Obj(Map<String, Json> members) implements Json {
}
record Arr(List<Json> elements) implements Json {
}
record Str(String value) implements Json {
}
record Num(BigDecimal value) implements Json {
}
record Bool(boolean value) implements Json {
}
record Null() implements Json {
}
static Obj parseObj(Reader reader) throws IOException {
skipWs(reader);
expect(reader, '{');
var members = new HashMap<String, Json>();
while (true) {
skipWs(reader);
if (peek(reader) == '}') {
expect(reader, '}');
return new Obj(Map.copyOf(members));
}
if (!members.isEmpty()) {
expect(reader, ',');
}
var str = parseStr(reader);
skipWs(reader);
expect(reader, ':');
var elem = parseValue(reader);
members.put(str.value(), elem);
}
}
static Arr parseArr(Reader reader) throws IOException {
skipWs(reader);
expect(reader, '[');
var elements = new ArrayList<Json>();
while (true) {
skipWs(reader);
if (peek(reader) == ']') {
expect(reader, ']');
return new Arr(List.copyOf(elements));
}
if (!elements.isEmpty()) {
expect(reader, ',');
}
var elem = parseValue(reader);
elements.add(elem);
}
}
static Json parseValue(Reader reader) throws IOException {
skipWs(reader);
char c = peek(reader);
return switch (c) {
case '"' -> parseStr(reader);
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> parseNum(reader);
case '{' -> parseObj(reader);
case '[' -> parseArr(reader);
case 't' -> {
expect(reader, 't');
expect(reader, 'r');
expect(reader, 'u');
expect(reader, 'e');
yield new Bool(true);
}
case 'f' -> {
expect(reader, 'f');
expect(reader, 'a');
expect(reader, 'l');
expect(reader, 's');
expect(reader, 'e');
yield new Bool(false);
}
case 'n' -> {
expect(reader, 'n');
expect(reader, 'u');
expect(reader, 'l');
expect(reader, 'l');
yield new Null();
}
default -> throw new RuntimeException("Unexpected character: '" + c + '\'');
};
}
static Str parseStr(Reader reader) throws IOException {
skipWs(reader);
expect(reader, '"');
var sb = new StringBuilder();
while (true) {
char c = read(reader);
if (c == '"') {
return new Str(sb.toString());
}
if (c != '\\') {
sb.append(c);
continue;
}
c = read(reader);
sb.append(switch (c) {
case '"' -> '"';
case '\\' -> '\\';
case '/' -> '/';
case 'b' -> '\b';
case 'f' -> '\f';
case 'n' -> '\n';
case 'r' -> '\r';
case 't' -> '\t';
case 'u' -> {
char c1 = read(reader);
char c2 = read(reader);
char c3 = read(reader);
char c4 = read(reader);
String hex = new String(new char[] {c1, c2, c3, c4});
yield (char) Integer.parseInt(hex, 16);
}
default -> throw new RuntimeException("Unexpected character: " + c);
});
}
}
static Num parseNum(Reader reader) throws IOException {
skipWs(reader);
var sb = new StringBuilder();
parseNumInteger(reader, sb);
char c = peek(reader);
if (c == '.') {
expect(reader, '.');
sb.append(c);
parseNumDigits(reader, sb);
c = peek(reader);
}
if (c == 'e' || c == 'E') {
expect(reader, c);
sb.append(c);
c = peek(reader);
if (c == '+' || c == '-') {
expect(reader, c);
sb.append(c);
}
parseNumDigits(reader, sb);
}
return new Json.Num(new BigDecimal(sb.toString()));
}
private static void parseNumInteger(Reader reader, StringBuilder sb) throws IOException {
char c = peek(reader);
if (c == '-') {
expect(reader, c);
sb.append(c);
c = peek(reader);
}
if (c == '0') {
expect(reader, c);
sb.append(c);
return;
}
parseNumDigits(reader, sb);
}
private static void parseNumDigits(Reader reader, StringBuilder sb) throws IOException {
char c = expect(reader, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
sb.append(c);
c = peek(reader);
while (c >= '0' && c <= '9') {
expect(reader, c);
sb.append(c);
c = peek(reader);
}
}
private static char read(Reader reader) throws IOException {
int c = reader.read();
if (c == -1) {
throw new RuntimeException("Unexpected EOF");
}
return (char) c;
}
private static void skipWs(Reader reader) throws IOException {
char c;
do {
reader.mark(1);
c = read(reader);
} while (c == ' ' || c == '\n' || c == '\r' || c == '\t');
reader.reset();
}
private static char peek(Reader reader) throws IOException {
reader.mark(1);
char c = read(reader);
reader.reset();
return c;
}
private static char expect(Reader reader, char... cs) throws IOException {
char c = read(reader);
for (char ec : cs) {
if (c == ec) {
return c;
}
}
if (cs.length == 0) {
throw new UnsupportedOperationException();
}
var sb = new StringBuilder("'").append(cs[0]).append("'");
for (int i = 1; i < cs.length; i++) {
sb.append('|').append("'").append(cs[i]).append("'");
}
throw new RuntimeException("Expected " + sb + " but got " + c);
}
static void write(Writer writer, Json value) throws IOException {
switch (value) {
case Str str -> writeString(writer, str.value());
case Num num -> writer.write(num.value().toString());
case Obj obj -> writeObj(writer, obj.members());
case Arr arr -> writeArr(writer, arr.elements());
case Bool bool -> writer.write(bool.value() ? "true" : "false");
case Null ignored -> writer.write("null");
}
}
private static void writeObj(Writer writer, Map<String, Json> members) throws IOException {
writer.write('{');
var it = members.entrySet().iterator();
if (it.hasNext()) {
var e = it.next();
writeString(writer, e.getKey());
writer.write(':');
write(writer, e.getValue());
while (it.hasNext()) {
e = it.next();
writer.write(',');
writeString(writer, e.getKey());
writer.write(':');
write(writer, e.getValue());
}
}
writer.write('}');
}
static void writeArr(Writer writer, List<Json> elements) throws IOException {
writer.write('[');
var it = elements.iterator();
if (it.hasNext()) {
write(writer, it.next());
while (it.hasNext()) {
writer.write(',');
write(writer, it.next());
}
}
writer.write(']');
}
private static void writeString(Writer writer, String str) throws IOException {
writer.write('"');
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
switch (c) {
case '"' -> writer.write("\\\"");
case '\\' -> writer.write("\\\\");
default -> writer.write(c);
}
}
writer.write('"');
}
}
Здравствуйте, B0FEE664, Вы писали:
BFE>Универсальный парсер этого знать не может. Это может знать только использующая парсер программа. Быть может это IP6 адрес, а может число с плавающей точкой, просто так получилось, что нет дробной части. Из самого формата никак не вывести, что это за значение.
Так парсеру ничего знать и не нужно. У формата есть несколько управляющих символов: {}[],:"
Вот посредством них входная строка делится на токены. Значение можно так строкой и хранить, т.к. она из файла и пришла.
Ну, а дальше клиентский код сам скажет что ему нужно. Просто нужны будут конвертеры из строки в нужные типы данных.
Зачем при чтении преобразовывать 12345 в int, если потом клиентский код может захотеть float? Чтобы добавлять лишние проверки и конвертации сначала из строки в int, а потом из int во float?
Re[9]: JSON vs BSON: очередное торжество больного воображени
Здравствуйте, νsb, Вы писали: νsb>Вроде написал, на простых тестах работает. Как раз где-то в два часа уложился. Старался всё в точности по спеке делать. Баги если и есть, то исправляемые и размер не увеличат. 229 строк. И форматтер еще строк на 50. νsb>...
Грамматика там действительно простая, вот только код у тебя по ходу рекурсивный. Лично у меня такой наивный рекурсивный подход даже на С++ не проканал, по памяти и скорости, с трудом представляю что там на Java будет.
Попробуй вот что-нибудь такое распарсить, можешь закрыть все скобки, если нравится :
Здравствуйте, Videoman, Вы писали:
V>Здравствуйте, νsb, Вы писали:
νsb>>Вроде написал, на простых тестах работает. Как раз где-то в два часа уложился. Старался всё в точности по спеке делать. Баги если и есть, то исправляемые и размер не увеличат. 229 строк. И форматтер еще строк на 50.
νsb>>...
V>Грамматика там действительно простая, вот только код у тебя по ходу рекурсивный. Лично у меня такой наивный рекурсивный подход даже на С++ не проканал, по памяти и скорости, с трудом представляю что там на Java будет. V>Попробуй вот что-нибудь такое распарсить, можешь закрыть все скобки, если нравится : V>
Между 2000 и 3000 стек оверфлу. Не, ну это уже из разряда про японскую бензопилу. Кому это надо. Уверен, что половина существующих реализаций сломаются раньше. По скорости работает моментально. По памяти хз, жаба есть жаба, мерять лень, но там стандартные структуры, которые в любой другой реализации будут плюс-минус такие же.
Здравствуйте, vsb, Вы писали: vsb>Между 2000 и 3000 стек оверфлу. Не, ну это уже из разряда про японскую бензопилу. Кому это надо. Уверен, что половина существующих реализаций сломаются раньше. По скорости работает моментально. По памяти хз, жаба есть жаба, мерять лень, но там стандартные структуры, которые в любой другой реализации будут плюс-минус такие же.
Отчасти согласен, но уж если делать свой велосипед, то пусть он будет лучше других. Сам этим же страдал, и у меня в библиотеках своя реализация, которая во первых жрёт всякую такую хрень, а также с флагами поддерживает несколько корневых JSON элементов и запятые в конце.
Вот еще проверь, там всё что в JSON-ах встречается — должен читаться:
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, swame, Вы писали:
S>>Я в итоге в поисках компромисса между читаемостью, расширяемостью, скоростью, размерам S>>пришел к псевдо-JSON Формату, где записи пакуются в строку, а названия полей описываются 1 раз.
V>Можно было обыграть нестрогим JSON (это который в синтаксисе Java Script, где названия полей всегда латинница, числа без кавычек). V>Тогда твой пример будет таким: V>
С виду для чтения человеком примерно то же самое, только в этом варианте парсинг строки внутри делается парсером,
за счет чего DOM при чтении/записи памяти займет в несколько раз больше,
и мой вариант все же компактней за счет отсутствия кавычек (но они могут быть, если есть неоднозначность).
V>Хотя, если уж самим писать парсер, то я бы убрал синтаксическую избыточность: V>
Мне не требуется писать свой парсер, у меня только Функция парсинга строки своя, экономичная и хорошо оттестированная.
Если данные не простые-повторяющиеся, как в примере, а сложно-вложенные,
ничего не мешает использовать обычный JSON, в других узлах того же файла.