1. 程式人生 > 實用技巧 >python json字串型別的value換行方案

python json字串型別的value換行方案

按照標準json語法,字串型別的value是不能換行寫的.
例如,以下是錯誤的寫法

{
    "key":"hello
 world"
}

但是遇到了需要在json中寫程式碼與伺服器互動的情況,無奈只能這樣寫:

{
    "key":"var a = 1\nvar b = 2\n var c=a+b"
}

程式碼行數少了還好,多了極其**,於是想辦法可以在json中換行寫程式碼.

嘗試1: json5

JSON5號稱"JSON for Humans",相比標準json主要特點如下:

  • 字串value支援換行
  • key可以不加""
  • key和value可以使用''代替""
  • 支援註釋
  • 二進位制數值

栗子:
以下是一個合法的json5

{
  // comments
  unquoted: 'and you can quote me on that',
  singleQuotes: 'I can use "double quotes" here',
  lineBreaks: "Look, Mom! \
No \\n's!",
  hexadecimal: 0xdecaf,
  leadingDecimalPoint: .8675309, andTrailing: 8675309.,
  positiveSign: +1,
  trailingComma: 'in objects', andIn: ['arrays',],
  "backwardsCompatible": "with JSON",
}

優點:現成

缺點:

  • 換行需要用 \ \標識;

  • 解析之後因為沒有了換行,程式碼無法執行,所以是達不到目的的;

{
    "key":"var a = 1\
var b = 2\
var c=a+b"
}

解析之後的結果為

{
    "key":"var a = 1var b = 2var c=a+b"
}

嘗試2: 編寫自己的json直譯器

  • 優點: 可根據自己定義的規則解析資料
  • 缺點: 成本高.

以下為用python實現的json直譯器,支援換行,目前沒有做完整的錯誤處理,請不要用於生產:

"""
遇到問題沒人解答?小編建立了一個Python學習交流群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
Topic: 下降解析器
Desc :
"""
import re
import collections

# Token specification
NUM = r'(?P<NUM>\d+)'
STR_D = r'"(?P<STR_D>[^"]*?)"'
STR_S = r'(?P<STR_S>\'.*?\')'
COLON = r'(?P<COLON>:)'
COMMA = r'(?P<COMMA>,)'
LLB = r'(?P<LLB>\[)'
RLB = r'(?P<RLB>\])'
LDB = r'(?P<LDB>\{)'
RDB = r'(?P<RDB>\})'
WS = r'(?P<WS>\s+)'
NULL = r'(?P<NULL>null)'
FALSE = r'(?P<FALSE>false)'
TRUE = r'(?P<TRUE>true)'

master_pat = re.compile('|'.join([NUM, STR_D, STR_S, LLB, RLB,
                                  LDB, RDB, COLON, COMMA, FALSE, TRUE, NULL, WS]), re.M | re.S)
# Tokenizer
Token = collections.namedtuple('Token', ['type', 'value'])


def generate_tokens(text):
    scanner = master_pat.scanner(text)
    for m in iter(scanner.match, None):
        tok = Token(m.lastgroup, m.group())
        if tok.type != 'WS':
            yield tok


def simple_join(text):
    li = []
    for t in generate_tokens(text):
        res = t[1]
        if t[0] == "STR_S":
            res = t[1].replace("'", "\"")
        elif t[0].startswith("STR_"):
            res = t[1].replace("\n", "\\n").replace("\r\n", "\\n")

        li.append(str(res))

    return "".join(li)


class Evaluator:
    def __init__(self, text):
        self.text = text

    def parse(self):
        self.tokens = generate_tokens(self.text)
        self.nexttok = next(self.tokens, None)
        self._advance()
        return self._parse()

    def _advance(self):
        '''Advance one token ahead'''
        self.tok, self.nexttok = self.nexttok, next(self.tokens, None)

    def _parse(self):
        if self.tok[0] == "LLB":
            return self.get_list()
        if self.tok[0] == "LDB":
            return self.get_dict()
        if self.tok[0].startswith("STR_"):
            return self.tok[1].strip('"')
        if self.tok[0] == "NUM":
            return int(self.tok[1])
        if self.tok[0] == "NULL":
            return None
        if self.tok[0] == "FALSE":
            return False
        if self.tok[0] == "TRUE":
            return True
        raise Exception("未知token:{}".format(self.tok[1]))

    def get_dict(self):
        """
        { 開頭
        中間內容必須為 k:v,
        結尾必須為 }
        """
        res = {}
        self._advance()
        # }
        if self.tok[0] == "RDB":
            return {}

        def parse_value():
            if not self.tok[0].startswith("STR_"):
                raise Exception(f"KEY需是字串形式,{self.tok[1]}")
            new_key = self._parse()
            self._advance()
            if self.tok[0] != "COLON":
                raise Exception("KEY和VALUE需用:分割")
            self._advance()
            res[new_key] = self._parse()
            self._advance()

        parse_value()
        while self.tok[0] == "COMMA":
            self._advance()
            parse_value()

        return res

    def get_list(self):
        res = []
        self._advance()
        # }
        if self.tok[0] == "RLB":
            return []

        def parse_value():
            if self.tok[0] in ["NUM", "STR_D", "STR_S", "NULL", "FALSE", "TRUE"]:
                res.append(self._parse())
            elif self.tok[0] == "LLB":
                res.append(self.get_list())
            elif self.tok[0] == "LDB":
                res.append(self.get_dict())

        parse_value()
        self._advance()
        while self.tok[0] == "COMMA":
            self._advance()
            parse_value()
            self._advance()
        return res


if __name__ == '__main__':
    text = """
        {
        "k1":1,
        "k2":"v2",
        "is_true":true,
        "is_none":null,
        "k2":"v2",
        "k3":{
        "a1":"a1",
        "a2":"a2",
        "code":"
        var code = 0
        code = 1
        "
        },
        "list":[1,2,3,4]
    }
    """
    # test(text)
    e = Evaluator(text)
    res = e.parse()
    print(res) #{'k1': 1, 'k2': 'v2', 'is_true': True, 'is_none': None, 'k3': {'a1': 'a1', 'a2': 'a2', 'code': '\n        var code = 0\n        code = 1\n        '}, 'list': [1, 2, 3, 4]}

嘗試3: 全域性替換 \n 為 \n

優點:簡單粗暴

{
    "key":"
var a = 1
var b = 2
var c=a+b"
}

替換之後的結果為

{
    "key":"\nvar a = 1\nvar b = 2\n var c=a+b"
}

缺點:雖然寫的時候有換行,但是上傳到伺服器再查詢的時候只能看到\n,依然缺乏可讀性;

嘗試4(最終採用方案):

結合UI將json生成樹狀節點,將程式碼型別的value單獨顯示.

優點

  • 難度一般
  • 按常規方式解析json
  • 上傳到伺服器再查詢的時候也能看到換行.