#python #json #shell #parsing #flatten
Вопрос:
Я пытаюсь разобрать и сгладить файл, подобный JSON, который выглядит следующим образом:
EventTime_t : 2021-07-23T23:03:41.711Z
FileName_s : \nb009dfsrootadminusershareskleindocumentsimporting ee data v5.0.pdf
FileAttributes_s : [
{
"Access": 70,
"Count": 3,
"FileType": "99c07caa-8fc4-4f94-b313-cb434493f900",
"UniqueCount": null,
"Attachment": null,
"Name": "Employee Details - U.S."
},
{
"Access": 93,
"Count": 11,
"FileType": "a44669fe-0d48-453d-a9b1-2cc83f2cba77",
"UniqueCount": null,
"Attachment": null,
"Name": "Portable stack (BS)"
}
]
FileUpdatedBy_s :
FileUpdatedDate_t : 2009-05-27T20:01:22Z
EventTime_t : 2021-07-23T23:04:03.862Z
FileName_s : \xdev1900.orgdfsrootadminusershareskleinaxn980test managementbare cards to link.xlsx
FileAttributes_s : [
{
"Access": 85,
"Count": 20,
"FileType": "50842eb7-edc8-4019-85dd-5a5c1f2bb085",
"UniqueCount": null,
"Attachment": null,
"Name": Plan Growth Number"
}
]
FileUpdatedBy_s : Mike
FileUpdatedDate_t : 1980-01-02T00:00:00Z
Я написал сценарий bash, но я не доволен тем, как я его написал.
#!/bin/bash
echo -n | tee col_1.txt col_2.txt col_3.txt col_4.txt col_5.txt col_6.txt col_7.txt col_8.txt col_9.txt col_10.txt
cat full.json | grep '"Access"' | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/"Access"/ amp;amp; seen[$0] )' > col_1.txt
cat full.json | grep '"Count"' | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/"Count"/ amp;amp; seen[$0] )' > col_2.txt
cat full.json | grep '"FileType"' | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/"FileType"/ amp;amp; seen[$0] )' > col_3.txt
cat full.json | grep '"UniqueCount"' | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/"UniqueCount"/ amp;amp; seen[$0] )' > col_4.txt
cat full.json | grep '"Attachment"' | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/"Attachment"/ amp;amp; seen[$0] )' > col_5.txt
cat full.json | grep '"Name"' | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/"Name"/ amp;amp; seen[$0] )' > col_6.txt
paste -d ',' col_1.txt col_2.txt col_3.txt col_4.txt col_5.txt col_6.txt | pr -t -e20 > output1.txt
sed -i 's/" *"/" "/g' output1.txt
sed -i 's/ */ /g' output1.txt
cat full.json | grep "EventTime_t" | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/EventTime_t/ amp;amp; seen[$0] )' > col_1.txt
cat full.json | grep "FileAttributes_s" | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/FileAttributes_s/ amp;amp; seen[$0] )' > col_2.txt
cat full.json | grep "FileUpdatedBy_s" | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/FileUpdatedBy_s/ amp;amp; seen[$0] )' > col_3.txt
cat full.json | grep "FileUpdatedDate_t" | sed -e 's/ */ /g' -e 's/:/n/g' | awk '!(/FileUpdatedDate_t/ amp;amp; seen[$0] )' > col_4.txt
paste -d ',' col_6.txt col_7.txt col_8.txt col_9.txt | pr -t -e20 > output2.txt
sed -i 's/" *"/" "/g' output2.txt
sed -i 's/ */ /g' output2.txt
ln=( $(grep -n "EventTime_t" full.json | cut -d ':' -f 1) )
last_line=`wc -l full.json | cut -d ' ' -f 1`
ln =(${last_line})
cat /dev/null > fnl_output.csv
echo "1|" > col_10.txt
j=1;i=0;
while [ $i -lt ${#ln[*]} ];
do
if [ -z ${ln[$j]} ]; then
paste col_10.txt output2.txt | pr -t -e20 > output3.txt
sed -e 's/" *"/ /g' -e 's/ */ /g' -e 's/null//g' output3.txt | awk '!seen[$0] ' > output2.txt
echo -n | tee output3.txt
while read line
do
count=`echo $line | cut -d'|' -f 1`
txt=`echo $line | cut -d'|' -f 2`
i=0
while [ $i -lt $count ]; do
echo $txt >> output3.txt
i=$(( $i 1));
done;
done < output2.txt;
paste -d ',' output3.txt output1.txt > fnl_output.csv
rm -f ./*.txt
exit 0;
else
if [ ${ln[$i]} -lt ${ln[$j]} ]; then
start=${ln[$i]};
end=`expr ${ln[$j]} - 1`;
sed -n "${start},${end}p" full.json > ${start}.txt
ln1=( $(grep -n '"Access"' ${start}.txt | cut -d ':' -f 1))
t=`echo ${#ln1[*]}`
echo $t"|" >> col_10.txt
j=$(( $j 1));
fi
i=$(( $i 1));
fi
done;
Я знаю, что это неудачный способ анализа JSON по ряду причин — использование пар ключ-значение путем форматирования каждого ключа, может не обрабатывать большие файлы JSON, создает много временных файлов и т. Д.
И у этого JSON может быть больше дополнительных атрибутов для анализа — поэтому каждый раз, когда я нахожу новый атрибут, я должен возвращаться к коду и обновлять его.
Вывод этого сценария оболочки должен представлять собой набор строк и столбцов в формате csv.
Может ли кто-нибудь помочь мне добиться того же в python? (Я попробовал то же самое в python, используя пакеты — ‘json’ и ‘pandas’, но они не распознают эти данные как правильный JSON) Примечание: В настоящее время размер входных файлов составляет от 50 до 100 МБ. Этот размер может вырасти в будущем.
Спасибо Лакшминарасу Чендури
Комментарии:
1. Это не «неправильный файл JSON», это просто формат, отличный от JSON.
2. Тем не менее, вопрос «как мне написать синтаксический анализатор для данных, примерно соответствующих этому примеру?» слишком широк, чтобы быть здесь в теме. «Как мне написать синтаксический анализатор?»-это, как правило, курс информатики 300-го уровня (часто первая половина вводного занятия по разработке компилятора). Описание того, как это сделать хорошо , — тема для книги. Описывая, как это сделать плохо … ну, зачем кому-то учить этому?
3. С другой стороны… у вас действительно есть встроенные данные JSON. Если вы можете отделить значения от ключей, то вы можете использовать реальный анализатор JSON для подмножества значений, которые на самом деле являются JSON.
4. Часть этих данных json также может быть нарушена. Просто добавлена попытка исправить ЭТУ строку.
Ответ №1:
Вам нужен какой-то индивидуальный код синтаксического анализа. Например, мы могли бы сначала разделить каждое событие в данных, а затем обработать каждое поле.
STRING = "string"
LIST = "list"
FIELD_TYPE_MAPPING = {
"EventTime_t" : STRING,
"FileName_s" : STRING,
"FileAttributes_s" : LIST,
"FileUpdatedBy_s": STRING,
"FileUpdatedDate_t": STRING
}
def process(data, fieldTypeMapping):
output = {}
mode = STRING
linesOfList = []
savedFieldName = None
for line in data:
# If we have : in a line, we may be starting a new field
if ':' in line:
components = line.split(':')
fieldName = components[0].strip()
if fieldName in fieldTypeMapping:
if mode == LIST:
# Process the collected lines in the list field. (Here we just merge them into one line.)
# We should have something like output[savedFiledName] = handleDataForListField(linesOfList)
output[savedFieldName] = "".join(linesOfList)
fieldType = fieldTypeMapping[fieldName]
if fieldType == STRING:
# We could have a customized handling function based on the field name here
output[fieldName] = line[len(components[0]) 1 :]
mode = STRING
elif fieldType == LIST:
mode = LIST
linesOfList = []
savedFieldName = fieldName
if mode == LIST:
linesOfList.append(line) # we could process the line here
return output
# Main Part
isInProgress = False
startPattern = 'EventTime_t'
endPattern = 'FileUpdatedDate_t'
# Process line by line
data = []
events = []
# Assume we process the data line by line
for line in input.split('n'):
if line.startswith(startPattern):
isInProgress = True
if isInProgress:
data.append(line)
if line.startswith(endPattern):
isInProgress = False
events.append(process(data, FIELD_TYPE_MAPPING))
# Testing Part
for item in events:
print(item)
Ответ №2:
Как бы то ни было, этот «json» может быть «исправлен» с помощью sed
fixedInner=$(
sed -re '
s/\/\\/g # escape
s/^([^ :["] ) : "?(.{2,}|$)"?/"1" : "2",/ # double quote keys and values
s/^([^ :["] )( ): [$/"1"2: [/ # double quote keys followed by [
s/"EventTime_t.*/{/; s/("FileUpdatedDate_t.*),/1},/ # add braces to object start and end
# add , for alone ]
s/ []] *$/],/ ' test.txt)
echo "[ ${fixedInner%?} ]" | jq -r '.'
Результат:
[
{
"EventTime_t": "2021-07-23T23:03:41.711Z",
"FileName_s": "\\nb009\dfsroot\admin\usershares\klein\documents\importing ee data v5.0.pdf",
"FileAttributes_s": [
{
"Access": 70,
"Count": 3,
"FileType": "99c07caa-8fc4-4f94-b313-cb434493f900",
"UniqueCount": null,
"Attachment": null,
"Name": "Employee Details - U.S."
},
{
"Access": 93,
"Count": 11,
"FileType": "a44669fe-0d48-453d-a9b1-2cc83f2cba77",
"UniqueCount": null,
"Attachment": null,
"Name": "Portable stack (BS)"
}
],
"FileUpdatedBy_s": "",
"FileUpdatedDate_t": "2009-05-27T20:01:22Z"
},
{
"EventTime_t": "2021-07-23T23:04:03.862Z",
"FileName_s": "\\xdev1900.org\dfsroot\admin\usershares\klein\axn980\test management\bare cards to link.xlsx",
"FileAttributes_s": [
{
"Access": 85,
"Count": 20,
"FileType": "50842eb7-edc8-4019-85dd-5a5c1f2bb085",
"UniqueCount": null,
"Attachment": null,
"Name": "Plan Growth Number"
}
],
"FileUpdatedBy_s": "Mike",
"FileUpdatedDate_t": "1980-01-02T00:00:00Z"
},
{}
]
Я оставляю на ваше усмотрение исправить это дело 🙂
"Name": Plan Growth Number"