脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Python - Python中优雅地处理JSON5文件的方法详解

Python中优雅地处理JSON5文件的方法详解

2024-04-23 16:12xiemingtian Python

JSON5 是 JSON 的一个超集,通过引入部分 ECMAScript 5.1 的特性来扩展 JSON 的语法,以减少 JSON 格式的某些限制,同时,保持兼容现有的 JSON 格式,本文给大家介绍了Python中如何优雅地处理 JSON5 文件,需要的朋友可以参考下

JSON5 概述

JSON5 是 JSON 的一个超集,通过引入部分 ECMAScript 5.1 的特性来扩展 JSON 的语法,以减少 JSON 格式的某些限制。同时,保持兼容现有的 JSON 格式。从官网作者介绍来看,JSON5 注重的是更人性化的编写和维护,一般用于软件的配置文件场景。

JSON5 拓展了 JSON 的能力,支持以下特性:

  • 注释

  • 尾随逗号

  • 单引号

  • 字符串字面量

  • 数字 (包括 Infinity, NaN, hexadecimal)

  • 对象/数组字面量

  • 多行字符串

格式官方文档 spec.json5.org/

遇到的问题

最近团队开始对接华为鸿蒙系统,在鸿蒙工程中,配置文件都是 json5 文件格式。例如存储 APP 信息的 app.json5

{
  "app": {
    // 包名
    "bundleName": "com.xxx.sample",
    // 厂家信息
    "vendor": "sample",
    // 版本号
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:app_launcher",
    "label": "$string:app_name",
    "generateBuildHash": true
  }
}

JSON5 文件中会存储数据信息和注释信息,有助于在阅读的时候了解数据结构。

需求:脚本修改 JSON5 文件时保留注释信息

需求:在 CD 构建时,根据传入的版本号修改 app.json5 中的版本号信息,然后将修改的文件提交到对应的版本分支上。

处理方式:因为是 python 的脚本,所以就找了 python 中可以操作 json5 的库,首先就是最常见的 json5 库,它提供与标准 json 库类似的 API,可以读写 json5 文件,例如下面的例子

import json5

data = json5.load(open('app.json5','r'))
print(json5.dumps(data, indent=4))
// 输出结果
{
    "app": {    
        "bundleName": "com.xxx.sample",
        "vendor": "sample",
        "versionCode": 1000000,
        "versionName": "1.0.0",
        "icon": "$media:app_launcher",
        "label": "$string:app_name",
        "generateBuildHash": true    
    }
}

可以看到虽然 API 可以正确的解析和输出文件数据,但是注释信息却没有了。使用官方推荐的 nodejs 版本 json5 库也是一样的结果。也有人开 issue 提需求是否可以提供 API 可以保留注释信息,但是作者最终还是暂时婉拒了这个需求

Python中优雅地处理JSON5文件的方法详解

后续虽然有人提交了支持这个 feature 的 PR,但是也是迟迟没有合入。所以目前来说较为官方的库都是没有支持读写 json5 文件时保留注释信息的。

虽然在搜索解决方案的时候,也有人提出说直接使用一个字段(例如"__comment__")存储注释信息,但是个人认为这是非常不优雅的:明明 JSON5 推出就是支持注释的,最终又要回退到 JSON。

解决方式

虽然官方库没有支持读写时保留注释信息,但是还是有部分扩展库是支持的。这些扩展库也在 JSON5 的 Github 的 Wiki 中 In-the-Wild 部分列举了出来。

json-five

其中 json-five 这个库支持读写 JSON5 文件时保留注释信息。

下面是官方提供的一个 demo

from json5.loader import loads, ModelLoader
from json5.dumper import dumps, ModelDumper
from json5.model import BlockComment
json_string = """{"foo": "bar"}"""model = loads(json_string, loader=ModelLoader())print(model.value.key_value_pairs[0].value.wsc_before)  # [' ']
model.value.key_value_pairs[0].key.wsc_before.append(BlockComment("/* comment */"))
dumps(model, dumper=ModelDumper()) # '{/* comment */"foo": "bar"}'

可以看出,虽然 json-five 支持了保留注释信息,但是在数据的操作上非常麻烦,基本不能像使用 json 库时将数据当做 dict 进行操作,这样很不优雅。

扩展 json-five

于是对现有数据结构进行了扩展,支持[]操作符进行获取或者赋值,简化 json5 操作流程。

# -*- coding: UTF-8 -*-
'''
支持保留注释和格式的JSON5处理工具
'''
# pip3 install json-five
import json5
from json5.dumper import modelize
from json5.model import JSONArray, JSONObject, String, JSONText, Value, KeyValuePair, walk

# 重写JSONObject的__getitem__方法,支持通过字符串获取值,如果不存在则返回None
def _find(self, key):
    if isinstance(key, str) and isinstance(self, JSONObject):
        for item in self.key_value_pairs:
            if isinstance(item.key, String):
                if item.key.characters == key:
                    return item.value
    elif isinstance(key, int) and isinstance(self, JSONArray):
        return self.values[key]
    elif isinstance(self, JSONText):
        return self.value[key]
    return None

# 重写JSONObject的__setitem__方法,支持通过字符串设置值,如果不存在则抛出异常
def _jsonobj_set(self: JSONObject, key: str, value: Value):
    new_item = KeyValuePair(modelize(key), value)
    for index in range(len(self.key_value_pairs)):
        item = self.key_value_pairs[index]
        if isinstance(item.key, String):
            if item.key.characters == key:
                old_value = self.values[index]
                new_item.value.wsc_after = old_value.wsc_after
                new_item.value.wsc_before = old_value.wsc_before
                new_item.value._tok = old_value._tok
                new_item.value._end_tok = old_value._end_tok
                self.values[index] = new_item.value
                return
    raise KeyError(key)
    # self.keys.append(new_item.key)
    # self.values.append(new_item.value)

# 重写JSONArray的__setitem__方法,支持通过整数设置值,如果不存在则抛出异常,如果存在则覆盖原值
def _jsonarray_set(self: JSONArray, index: int, value: Value):
    self.values[index] = value

# 重写JSONObject的str_keys方法,支持返回所有字符串类型的keys,如果不存在则返回[]
def _jsonobj_str_keys(self: JSONObject):
    return [item.characters for item in self.keys if isinstance(item, String)]

JSONObject.__getitem__ = _find
JSONObject.__setitem__ = _jsonobj_set
JSONObject.str_keys = _jsonobj_str_keys
JSONArray.__getitem__ = _find
JSONArray.__setitem__ = _jsonarray_set
JSONText.__getitem__ = _find

# 加载JSON5文件,保留注释和格式,返回一个Model对象
def loadjson5_with_comment(path: str):
    return json5.load(open(path, 'r'), loader=json5.loader.ModelLoader())

# 保存JSON5文件,保留注释和格式
def savejson5_with_comment(data, path: str):
    return json5.dump(data, open(path, 'w'), dumper=json5.dumper.ModelDumper())

# 寻找所有JSONObject中key为keyword的对象,返回一个列表,如果不存在则返回[]
def find_jsonobjects(model, keyword: str) -> list[JSONObject]:
    items = []
    for item in walk(model):
        if isinstance(item, JSONObject):
            for key in item.keys:
                if isinstance(key, String) and key.characters == keyword:
                    items.append(item)
    return items

最终实现以下效果,最大限度地保留的文件格式和注释信息,优雅地满足了需求

file_path = 'app.json5'
model = loadjson5_with_comment(file_path)
model['app']['versionName'] = modelize('1.1.1')
savejson5_with_comment(model, file_path)
# 修改后文件内容
{
  "app": {
    // 包名
    "bundleName": "com.xxx.sample",
    // 厂家信息
    "vendor": "sample",
    // 版本号
    "versionCode": 1000000,
    "versionName": '1.1.1',
    "icon": "$media:app_launcher",
    "label": "$string:app_name",
    "generateBuildHash": true
  }
}

其他库

从 In-the-Wild 中可以看到有很多库支持 json5,但是测试前面的几个 python 和 js 的库,目前只有 json-five 这个支持保留注释信息(也可能是我使用姿势问题?)

总结

JSON5 作为 JSON 的扩展,提供了更人性化的语法,非常适合静态配置文件场景,可以目前官方的库 API 读写文件时不支持保留注释信息(往往可能是配置文件中关键信息),在一些自动化场景稍显不便。

虽然目前可以通过三方库+扩展的方式达到一个基本可用的状态,还是希望官方能对此能力进行支持,让 JSON5 的处理更优雅~

以上就是Python中优雅地处理JSON5文件的方法详解的详细内容,更多关于Python处理JSON5文件的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/7360493040137175078

延伸 · 阅读

精彩推荐
  • Python2022年Python图形界面框架推荐

    2022年Python图形界面框架推荐

    本文为大家推荐2022年Python图形界面框架,有需要的朋友可以参考下....

    今日头条10722021-11-08
  • PythonPython基础之循环语句用法示例【for、while循环】

    Python基础之循环语句用法示例【for、while循环】

    这篇文章主要介绍了Python基础之循环语句用法,结合实例形式分析了Python使用for、while循环及range、break和continue语句相关使用技巧,需要的朋友可以参考下...

    流年醉影6372021-06-08
  • Python对pandas中to_dict的用法详解

    对pandas中to_dict的用法详解

    今天小编就为大家分享一篇对pandas中to_dict的用法详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    积跬步___至千里8502021-03-01
  • Python详解python配置虚拟环境

    详解python配置虚拟环境

    这篇文章主要介绍了python配置虚拟环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编...

    无霸独尊5902021-06-14
  • Python解决django前后端分离csrf验证的问题

    解决django前后端分离csrf验证的问题

    今天小编就为大家分享一篇解决django前后端分离csrf验证的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    HYESC8612021-05-26
  • PythonPython/Django后端使用PIL Image生成头像缩略图

    Python/Django后端使用PIL Image生成头像缩略图

    这篇文章主要为大家详细介绍了Python/Django后端使用PIL Image生成头像缩略图,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Lockeyi4292021-06-22
  • Python浅谈Pandas中map, applymap and apply的区别

    浅谈Pandas中map, applymap and apply的区别

    下面小编就为大家分享一篇浅谈Pandas中map, applymap and apply的区别,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    小强的呼呼呼7872021-01-29
  • PythonPython二元算术运算常用方法解析

    Python二元算术运算常用方法解析

    这篇文章主要介绍了Python二元算术运算常用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以...

    小几斤2102020-09-15