В Python мне приходится сохранять в файл хранилища массив пользовательских данных, состоящий из объектов JSON (простой формат: [{}, {}...]). По мере того, как программа собирает и записывает пользовательские данные, массив [] потенциально увеличивается до нескольких тысяч объектов. Каждый объект {} состоит примерно из пятидесяти пар ключ_значение. Как мне написать специальную функцию для json.dump(), используя собственный kwarg "default=", который будет начинать каждый объект с новой строки и добавлять между ними дополнительное строковое пространство? Использование kwarg "indent=" для красивой печати массива делает файл слишком длинным по вертикали, и без этого kwarg я получаю плотную массу трудночитаемых объектов.
Чтобы проиллюстрировать проблему, возьмем гораздо более короткий массив.
example = [{"a":1, "b":2, "c":3,}, {"d":1, "e":2, "f":3,}, {"g":1, "h":2, "i":3}, {"j":6, "k":7, "l":8}]
Без использования отступов вывод в мой файл .json будет слишком компактным.
[{"a": 1, "b": 2, "c": 3}, {"d": 1, "e": 2, "f": 3}, {"g": 1, "h": 2, "i": 3}, {"j": 6, "k": 7, "l": 8}]
Если я использую отступ, я получаю узкий вертикальный столбец.
[
{
"a": 1,
"b": 2,
"c": 3
},
{
"d": 1,
"e": 2,
"f": 3
},
{
"g": 1,
"h": 2,
"i": 3
},
{
"j": 6,
"k": 7,
"l": 8
}
]
Чтобы свести к минимуму пробелы, сохраняя при этом читабельность, я хочу, чтобы вывод был в этом формате (здесь он организован вручную).
[
{"a": 1, "b": 2, "c": 3},
{"d": 1, "e": 2, "f": 3},
{"g": 1, "h": 2, "i": 3},
{"j": 6, "k": 7, "l": 8}
]
Когда каждый объект содержит до пятидесяти пар ключ:значение, этот формат, даже с переносом, достигает обоих целей. Я удивлен, что метод не предоставляет такой возможности. Но я не могу найти достаточно информации об основах кода JSON, чтобы вводить символы новой строки и дополнительные пробелы в существующем методе json.dump(). Любая помощь?
Если ваш объект верхнего уровня представляет собой массив, рассмотрите вместо этого формат строк JSON (JSONL). Это буквально один объект JSON на строку без [] снаружи. Преимущество состоит в том, что вы можете легко добавлять данные в JSONL, не переписывая весь файл, что очень повышает эффективность. Читается тоже легко: просто for line in file: json.loads(line)
.
Если вы пытаетесь минимизировать пробелы, почему между каждым словарем в выводе появляется новая строка? Это, конечно, не функциональное требование.
@SIGHUP: Поскольку в моей реальной программе словари гораздо длиннее, дополнительные строчные пробелы составляют относительно небольшую часть общего пространства — и это того стоит для улучшения читаемости.
Все ответы подчеркивают точку зрения Бармара и diggusbickus о том, что то, что я хотел сделать, не может быть сделано изначально, а должно быть достигнуто с помощью обходного пути. Я протестировал фрагмент пользователя user24714692, и он сработал отлично, создав файл JSON, который мог загрузить json.load(). SIGHUP я не тестировал, так как начинаю со словарей без отступов и мне приходится записывать их в файл. Часть сегодняшнего дня я провел за чтением документации по JSON Lines. В конце концов, преимущество возможности каждый раз добавлять, а не перезаписывать весь файл данных, привлекло меня к решению @nneonneo.
Вы можете использовать модуль json
с dumps()
:
import json
def _dump_one(example, p):
with open(p, 'w') as f:
f.write('[\n')
for i, e in enumerate(example):
s = json.dumps(e)
f.write(f'\t{s}')
if i < len(example) - 1:
f.write(',\n\n')
else:
f.write('\n')
f.write(']\n')
example = [
{"a": 1, "b": 2, "c": 3},
{"d": 1, "e": 2, "f": 3},
{"g": 1, "h": 2, "i": 3},
{"j": 6, "k": 7, "l": 8}
]
_dump_one(example, 'file.json')
Вы также можете write()
это сделать только один раз:
import json
def _dump_two(example, p):
res = '[\n'
for i, e in enumerate(example):
s = json.dumps(e)
res += f'\t{s}'
if i < len(example) - 1:
res += ',\n\n'
else:
res += '\n'
res += ']\n'
with open(p, 'w') as f:
f.write(res)
example = [
{"a": 1, "b": 2, "c": 3},
{"d": 1, "e": 2, "f": 3},
{"g": 1, "h": 2, "i": 3},
{"j": 6, "k": 7, "l": 8}
]
_dump_two(example, 'file.json')
Если ваш объект верхнего уровня представляет собой массив, рассмотрите вместо этого формат JSON Lines (JSONL). Это буквально один объект JSON на строку без [] снаружи. Преимущество заключается в том, что вы можете легко добавлять данные в JSONL, не переписывая весь файл, что очень важно для эффективности: вызов dump
для создания дампа постоянно расширяющегося массива приведет к линейному замедлению каждого дампа (общее квадратичное время!), тогда как добавление к строкам JSON файл имеет постоянное время (общее линейное время для всех записей).
Чтобы добавить новые объекты в файл строк JSON:
with open("output.jsonl", "a") as outf:
json.dump(new_record, outf)
outf.write("\n")
Чтобы прочитать все записи из файла JSON Lines:
with open("output.jsonl", "r") as inf:
for row in inf:
obj = json.loads(row)
# then process obj or append it to a list, etc.
Очень полезное предложение. Однако в моей ситуации я обнаружил, что мне приходится добавлять новые объекты, используя собственный специальный синтаксис JSON Lines, открываясь с помощью «with jsonlines.open(file, mode='a') aswriter:», а затем записывая с помощью «writer.write». (новый_obj)». После записи нового объекта в файл автоматически создается новая пронумерованная строка с курсором, готовая к приему другого объекта. JSON.dump() просто добавляет новые объекты к существующей строке — не совсем тот результат, который я надеялся получить.
Как мне написать специальную функцию для json.dump(), используя собственный kwarg "default=", который будет начинать каждый объект с новой строки и добавлять между ними дополнительное строковое пространство?
ты не можешь. default
будет применяться только к значениям, а не к «строкам», и даже тогда ваши значения сериализуемы.
Если указано, по умолчанию должна быть функция, которая вызывается для объектов, которые иначе нельзя сериализовать — Документация модуля json
Вы можете использовать json.dumps() для форматирования отдельных словарей и добавления форматированного вывода в список. Впоследствии вы можете использовать str.join(), чтобы получить требуемый формат вывода.
import json
a = [
{
"a": 1,
"b": 2,
"c": 3
},
{
"d": 1,
"e": 2,
"f": 3
},
{
"g": 1,
"h": 2,
"i": 3
},
{
"j": 6,
"k": 7,
"l": 8
}
]
d = [json.dumps(_d) for _d in a]
print("[\n\t" + ",\n\n\t".join(d) + "\n]")
Выход:
[
{"a": 1, "b": 2, "c": 3},
{"d": 1, "e": 2, "f": 3},
{"g": 1, "h": 2, "i": 3},
{"j": 6, "k": 7, "l": 8}
]
Почему вы удивлены? Ничего подобного нет ни в JavaScript, ни в PHP. Как и Python, они предоставляют выбор только между одной строкой и всем, что находится в новой строке. Детальный контроль форматирования невозможен.