programing

Python에서 느린 JSON 처리 - '속성 이름 필요'

closeapi 2023. 3. 27. 21:16
반응형

Python에서 느린 JSON 처리 - '속성 이름 필요'

Pythons(2.7) 'json' 모듈을 사용하여 다양한 JSON 피드를 처리하려고 합니다.유감스럽게도 이러한 피드 중 일부는 JSON 표준을 준수하지 않습니다. 특히 일부 키는 이중 음성 기호("")로 둘러싸여 있지 않습니다.이로 인해 Python이 버그아웃됩니다.

수신 데이터를 해석하고 복구하기 위해 못생긴 코드를 작성하기 전에 Python이 이 잘못된 형식의 JSON을 해석하거나 데이터를 "복구"하여 유효한 JSON이 되도록 할 수 있는 방법이 없을까?

작업 예

import json
>>> json.loads('{"key1":1,"key2":2,"key3":3}')
{'key3': 3, 'key2': 2, 'key1': 1}

깨진 예

import json
>>> json.loads('{key1:1,key2:2,key3:3}')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\json\__init__.py", line 310, in loads
    return _default_decoder.decode(s)
  File "C:\Python27\lib\json\decoder.py", line 346, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Python27\lib\json\decoder.py", line 362, in raw_decode
    obj, end = self.scan_once(s, idx)
ValueError: Expecting property name: line 1 column 1 (char 1)

이 특정 프로바이더로부터의 JSON을 수정하기 위해 작은 REGEX를 작성했습니다만, 이것이 향후의 문제가 될 것으로 생각됩니다.제가 생각해낸 것은 다음과 같습니다.

>>> import re
>>> s = '{key1:1,key2:2,key3:3}'
>>> s = re.sub('([{,])([^{:\s"]*):', lambda m: '%s"%s":'%(m.group(1),m.group(2)),s)
>>> s
'{"key1":1,"key2":2,"key3":3}'

JSON 파서를 사용하여 JSON이 아닌 것을 해석하려고 합니다.가장 좋은 방법은 피드를 만든 사람이 수정하도록 하는 것입니다.

그게 항상 가능한 것은 아니라는 걸 이해합니다.파손 정도에 따라 정규식을 사용하여 데이터를 수정할 수 있습니다.

j = re.sub(r"{\s*(\w)", r'{"\1', j)
j = re.sub(r",\s*(\w)", r',"\1', j)
j = re.sub(r"(\w):", r'\1":', j)

다른 옵션은 엄격하지 않은 모드에서 json을 해석할 수 있는 demjson 모듈을 사용하는 것입니다.

네드와 치즈인버트가 지적한 정규 표현은 경기가 끈 안에 있을 때 고려되지 않는다.

다음 예를 참조하십시오(치즈 인버트의 용액 사용).

>>> fixLazyJsonWithRegex ('{ key : "a { a : b }", }')
'{ "key" : "a { "a": b }" }'

문제는 예상되는 출력은 다음과 같습니다.

'{ "key" : "a { a : b }" }'

JSON 토큰은 python 토큰의 서브셋이기 때문에 python의 tokenize 모듈을 사용할 수 있습니다.

제가 틀렸다면 수정해 주세요.하지만 다음 코드는 모든 경우에 느린 json 문자열을 수정해 줍니다.

import tokenize
import token
from StringIO import StringIO

def fixLazyJson (in_text):
  tokengen = tokenize.generate_tokens(StringIO(in_text).readline)

  result = []
  for tokid, tokval, _, _, _ in tokengen:
    # fix unquoted strings
    if (tokid == token.NAME):
      if tokval not in ['true', 'false', 'null', '-Infinity', 'Infinity', 'NaN']:
        tokid = token.STRING
        tokval = u'"%s"' % tokval

    # fix single-quoted strings
    elif (tokid == token.STRING):
      if tokval.startswith ("'"):
        tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"')

    # remove invalid commas
    elif (tokid == token.OP) and ((tokval == '}') or (tokval == ']')):
      if (len(result) > 0) and (result[-1][1] == ','):
        result.pop()

    # fix single-quoted strings
    elif (tokid == token.STRING):
      if tokval.startswith ("'"):
        tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"')

    result.append((tokid, tokval))

  return tokenize.untokenize(result)

따라서 json 문자열을 해석하기 위해 json.loads가 실패하면 fixLazyJson에 대한 콜을 캡슐화할 수 있습니다(정확한 형식의 json에 대한 성능 저하를 피하기 위해).

import json

def json_decode (json_string, *args, **kwargs):
  try:
    json.loads (json_string, *args, **kwargs)
  except:
    json_string = fixLazyJson (json_string)
    json.loads (json_string, *args, **kwargs)

lazy json을 수정할 때 나타나는 유일한 문제는 json이 잘못된 형식일 경우 두 번째 json.loads에 의해 발생한 오류가 원래 문자열의 행과 열을 참조하는 것이 아니라 수정된 문자열을 참조하는 것입니다.

마지막으로, 문자열 대신 파일 개체를 받아들이는 방법을 업데이트하는 것이 간단하다는 점을 지적하고 싶습니다.

보너스: 이와는 별도로 사람들은 보통 설정 파일에 json을 사용할 때 C/C++ 코멘트를 포함하기를 좋아합니다.이 경우 정규 표현을 사용하여 코멘트를 삭제하거나 확장 버전을 사용하여 json 문자열을 한 번에 수정할 수 있습니다.

import tokenize
import token
from StringIO import StringIO

def fixLazyJsonWithComments (in_text):
  """ Same as fixLazyJson but removing comments as well
  """
  result = []
  tokengen = tokenize.generate_tokens(StringIO(in_text).readline)

  sline_comment = False
  mline_comment = False
  last_token = ''

  for tokid, tokval, _, _, _ in tokengen:

    # ignore single line and multi line comments
    if sline_comment:
      if (tokid == token.NEWLINE) or (tokid == tokenize.NL):
        sline_comment = False
      continue

    # ignore multi line comments
    if mline_comment:
      if (last_token == '*') and (tokval == '/'):
        mline_comment = False
      last_token = tokval
      continue

    # fix unquoted strings
    if (tokid == token.NAME):
      if tokval not in ['true', 'false', 'null', '-Infinity', 'Infinity', 'NaN']:
        tokid = token.STRING
        tokval = u'"%s"' % tokval

    # fix single-quoted strings
    elif (tokid == token.STRING):
      if tokval.startswith ("'"):
        tokval = u'"%s"' % tokval[1:-1].replace ('"', '\\"')

    # remove invalid commas
    elif (tokid == token.OP) and ((tokval == '}') or (tokval == ']')):
      if (len(result) > 0) and (result[-1][1] == ','):
        result.pop()

    # detect single-line comments
    elif tokval == "//":
      sline_comment = True
      continue

    # detect multiline comments
    elif (last_token == '/') and (tokval == '*'):
      result.pop() # remove previous token
      mline_comment = True
      continue

    result.append((tokid, tokval))
    last_token = tokval

  return tokenize.untokenize(result)

네드의 제안에 따라 다음과 같은 것이 도움이 되었습니다.

j = re.sub(r"{\s*'?(\w)", r'{"\1', j)
j = re.sub(r",\s*'?(\w)", r',"\1', j)
j = re.sub(r"(\w)'?\s*:", r'\1":', j)
j = re.sub(r":\s*'(\w+)'\s*([,}])", r':"\1"\2', j)

비슷한 경우, 저는 .AFIK를 사용했는데, 이것은 상수가 계속 될 때만 작동되지 않습니다.nullPython (Python)에)None

계시니까null/None하다

import ast
decoded_object= ast.literal_eval(json_encoded_text)

에 Neds를 합니다.(?!/) url의 문제를 .

j = re.sub(r"{\s*'?(\w)", r'{"\1', j)
j = re.sub(r",\s*'?(\w)", r',"\1', j)
j = re.sub(r"(\w)'?\s*:(?!/)", r'\1":', j)
j = re.sub(r":\s*'(\w+)'\s*([,}])", r':"\1"\2', j) 
j = re.sub(r",\s*]", "]", j)

언급URL : https://stackoverflow.com/questions/4033633/handling-lazy-json-in-python-expecting-property-name

반응형