Синтаксический анализ деформированного JSON с использованием оболочки / Python

#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"