配置文件(JSON格式)替换工具(添加增加键值功能)

实际使用中发现,在进行配置时,对增加键值的需要还是很旺盛的,比如一个账号列表配置要新增账号、一个配置JSON Array增加完整的JSON配置块等等,鉴于此,更新了工具,满足此类需求。

1.分析

增加配置涉及的类型基本有这么几种:

  • JSON Object增加新的键值对,值为普通字符串
  • JSON Array内部是一个个字符串,如:['u1001','U1002']
  • JSON Object增加新的键值对,值是复杂的JSON Obect或者JSON Array
  • JSON Array内部是一个个JSON Object,如:
[
            {
                "productId":"commonProduct_001",
                "productName":"矿泉水",
                "productPrice":"2.00"
            },
            {
                "productId":"commonProduct_002",
                "productName":"冰可乐",
                "productPrice":"3.50"
            }
        ]

前2种很好解决,对一个JSON对象判断键在不在,如果在就是替换、不在就是增加,且增加都是字符串类型,直接替换原值就行,后面2种就显得麻烦些。

2.实现

在之前文章实现的基础上,重点改造点主要有以下几方面:

  • 对替换JSON Object部分改造,替换某个key前需要先判断它在不在当前JSON中
  • 对替换JSON Array部分改造,替换某个index指向的array时先判断array的长度是否大于index,如果大于则是替换,反之是增加。
  • 增加逻辑中对待增加的值进行JSON合法性检测,是非法的JSON结构以普通字符串添加,否则以解析完成的字典格式添加。

2.1 检测字符串是否为JSON

基本原理是利用json.loads()尝试解析字符串,如果不是合法json格式,则会抛出ValueError异常。

def is_json(json_str):
    '''
    判读是否合法的JSON字符串
    '''
    try:
        json.loads(json_str)
    except ValueError:
        return False
    return True

测试的结果如下:


image.png

2.2 替换/增加字符串逻辑

涉及代码片段:

    def json_replace_object(self, conf, key, val):
        '''
        对json的指定key替换值
        ''' 
        if(not conf.has_key(key)):
            print(u'增加key为:%s、val为:%s键值对' % (key, val))
        # 增加或替换值
        if(is_json(val)):
            # 增加的是json类型的值
            conf[key] = json.loads(val, object_pairs_hook=collections.OrderedDict)
            #print conf[key]
        else:
            # 增加的是str类型的值
            conf[key] = val
        # 返回
        return conf      

处理逻辑:

  • 检测JSON/字典是否含有指定的key,没有则添加、有则替换。不过对于python添加、替换操作可合二为一。
  • 判断待参数val是否为合法的JSON字符串,是则转为JSON对象在添加或替换,否则作为原始字符串添加或替换。

2.3 替换或增加JSON Array字符串元素

涉及代码片段:

  def json_replace_array(self, conf_arrary, index, val):
        '''
        Json Array替换值
        ''' 
        if(len(conf_arrary) <= index):
            print(u'增加:%s到%s中' % (val, conf_arrary))
            # 增加
            if(is_json(val)):
                # 增加的是json类型的值
                conf_arrary.insert(index,json.loads(val, object_pairs_hook=collections.OrderedDict))
            else:
                # 增加的是str类型的值
                conf_arrary.insert(index,val)
        else:
            #替换值
            print(u'将%s的第%s个元素替换为:%s' % (conf_arrary, index, val))
            conf_arrary[index] = val
        # 返回
        return conf_arrary

对JSON Array处理,关注参数index,它表示需要替换或者增加的JSON Array索引值,所以大致逻辑是:

  • 如果索引值index小于JSON Array长度,只是替换,且替换的值为普通字符串(暂时不支持这样替换JSON Array中完整的JSON对象的操作,如果需要这里也是判断参数值是否为JSON字符串就可以)
  • 如果索引值index大于或等于JSON Array长度,就是需要增加array元素的情况了,在依据值是否为合法JSON字符串处理:
    • 是普通字符串,以字符串方式增加
    • 是合法JSON字符串,解析为字典,添加到目标字典中

2.4 JSON Array的子JSON Object新增或替换

这种事比较复杂的一种情形,举几个例子:

  • 新增普通键值对
    假设文章开头JSON例子要在JSON Array的第一个元素新增,形成下面的JSON:
{
    "productId": "modifiedSpecialityProduct_001",
    "productName": "椰子糖(无糖型)",
    "productPrice": "30.00",
    "addKey": "addKey-val"
},
{
    "productId": "specialityProduct_002",
    "productName": "芒果干",
    "productPrice": "35.00"
}
  • 新增JSON Array元素
"productList": [
            {
                "productId": "modifiedSpecialityProduct_001",
                "productName": "椰子糖(无糖型)",
                "productPrice": "30.00"
            },
            {
                "productId": "specialityProduct_002",
                "productName": "芒果干",
                "productPrice": "35.00"
            },
            {
                "productId": "specialityProduct_002",
                "productName": "榴莲糖",
                "productPrice": "35.00"
            }
        ]

相关的代码片段:

        if(len(key_pattern.split('.')) == 1):
            if(not '#' in key_pattern):
                return self.json_replace_object(conf, key_pattern, val)     
            else:
               real_key = key_pattern.split('#')[0]
               index = int(key_pattern.split('#')[1])
               conf_arrary = conf[real_key]
               replaced_array = self.json_replace_array(conf_arrary, index, val)
               conf[real_key] = replaced_array
               return conf
        else:
            key = key_pattern.split('.')[0]
            if '#' in key:
                # 剔除#index拿到key
                real_key = key.split('#')[0]
                # 从#index拿到array的index
                index = int(key.split('#')[1])
                # 先取的array,在从array中按照index取出需要的
                conf_arrary = conf[real_key]
                if(len(conf_arrary) <= index):
                    # 从第0个copy
                    conf_arrary.insert(index,conf_arrary[0])
                    real_conf = conf_arrary[index]
                else:
                    real_conf = conf_arrary[index]
                # 对待替换的配置继续递归处理
                replaced_conf = self.json_replace_recursive(real_conf, key_pattern[key_pattern.index('.')+1:], val)
                # 修改好的值替换掉原本的这个index的array中的值
                if(len(conf_arrary) <= index):
                    conf_arrary.insert(index,replaced_conf)
                else:
                    conf_arrary[index] = replaced_conf
                # 再将这个array赋值回原本json的这个key的部分,达到改变配置效果
                conf[real_key] = conf_arrary
                # 返回调用者的是对原始json替换后的
                return conf

相关逻辑都在判断key按照“.”拆分后能拆分列表大小,只能拆分为1说明到达目标key那一级,反之需要递归处理,所以逻辑都在else处理的递归逻辑中。
按照之前定义,只有“#”指定一个JSON Array的索引值,对解析到key包含“#”,我们就知道是要进行元素替换或者新增了。
跟上面类似,这里逻辑是:

  • 索引值index小于array长度,替换操作,只是需要递归替换,因为key还含有“.”
  • 索引值index大于或等于array长度,增加操作:先把Array的地1个部分copy到array的index位置,再作为参数交给递归方法处理;处理完的Array,在赋值回原始的array对应的key,实现替换。

这里这个copy操作是有bug的,如果本身是空的array,就会出问题,copy不到值,且递归时检测是否含有子key也会出错。

2.5 完整替换逻辑

如下是替换或新增的逻辑:

def is_json(json_str):
    '''
    判读是否合法的JSON字符串
    '''
    try:
        json.loads(json_str)
    except ValueError:
        return False
    return True

class ContentModifier(object):
    '''
    配置内容修改器,依据配置项的key-val对,进行配置文件的修改
    '''
    def __init__(self, conf_paraser):
        '''
        初始化方法
        '''
        self.conf_paraser = conf_paraser       
        
    def json_replace_object(self, conf, key, val):
        '''
        对json的指定key替换值
        ''' 
        if(not conf.has_key(key)):
            print(u'增加key为:%s、val为:%s键值对' % (key, val))
        # 增加或替换值
        if(is_json(val)):
            # 增加的是json类型的值
            conf[key] = json.loads(val, object_pairs_hook=collections.OrderedDict)
            #print conf[key]
        else:
            # 增加的是str类型的值
            conf[key] = val
        # 返回
        return conf      
        
    def json_replace_array(self, conf_arrary, index, val):
        '''
        Json Array替换值
        ''' 
        if(len(conf_arrary) <= index):
            print(u'增加:%s到%s中' % (val, conf_arrary))
            # 增加
            if(is_json(val)):
                # 增加的是json类型的值
                conf_arrary.insert(index,json.loads(val, object_pairs_hook=collections.OrderedDict))
            else:
                # 增加的是str类型的值
                conf_arrary.insert(index,val)
        else:
            #替换值
            print(u'将%s的第%s个元素替换为:%s' % (conf_arrary, index, val))
            conf_arrary[index] = val
        # 返回
        return conf_arrary
                
    def json_replace_recursive(self, conf, key_pattern, val):
        '''
        按照key_pattern递归到最后一层,将其值修改为传入的val
        以CsvFileExportToCoreService#0.exportRules#0.fileExportRules.rule为例,表示:
            待修改的值在一级keyCsvFileExportToCoreService的值中,且它是array,#0指明要修改的在array的第一个
            待修改的值在第一个array的key为exportRules中,这个exportRules的值也是array,#0需要修改的指明要修改的在array的第一个
            待修改的值在第一个array的fileExportRules指定值中,此为json对象
            待修改的值在json对象的rule中
        '''
        print '-------%s : %s' % (key_pattern, val)
        if(len(key_pattern.split('.')) == 1):
            if(not '#' in key_pattern):
                return self.json_replace_object(conf, key_pattern, val)     
            else:
               real_key = key_pattern.split('#')[0]
               index = int(key_pattern.split('#')[1])
               conf_arrary = conf[real_key]
               replaced_array = self.json_replace_array(conf_arrary, index, val)
               conf[real_key] = replaced_array
               return conf
        else:
            key = key_pattern.split('.')[0]
            if '#' in key:
                # 剔除#index拿到key
                real_key = key.split('#')[0]
                # 从#index拿到array的index
                index = int(key.split('#')[1])
                # 先取的array,在从array中按照index取出需要的
                conf_arrary = conf[real_key]
                if(len(conf_arrary) <= index):
                    # 从第0个copy
                    conf_arrary.insert(index,conf_arrary[0])
                    real_conf = conf_arrary[index]
                else:
                    real_conf = conf_arrary[index]
                # 对待替换的配置继续递归处理
                replaced_conf = self.json_replace_recursive(real_conf, key_pattern[key_pattern.index('.')+1:], val)
                # 修改好的值替换掉原本的这个index的array中的值
                if(len(conf_arrary) <= index):
                    conf_arrary.insert(index,replaced_conf)
                else:
                    conf_arrary[index] = replaced_conf
                # 再将这个array赋值回原本json的这个key的部分,达到改变配置效果
                conf[real_key] = conf_arrary
                # 返回调用者的是对原始json替换后的
                return conf
            else:
                # 不是array类型,直接取出值进行递归替换
                # print '========== ' + key_pattern[key_pattern.index('.')+1:]
                replaced_conf = self.json_replace_recursive(conf[key], key_pattern[key_pattern.index('.')+1:], val)
                # 修改好的json替换原始json
                conf[key] = replaced_conf
                # 返回替换后的原始json
                return conf
            
    def json_modify(self, section, content):
        '''
        按照配置conf,取出其section段配置,对content进行修改
        '''
        #print content
        replaced_json = content
        if(not self.conf_paraser.exist_section(section)):
            raise RuntimeError(u'配置文件:%s没有section名为:%s的配置' % (self.conf_paraser.path, section))
        else:
            items = self.conf_paraser.get_section_items(section)
            # 替换所有需要的项
            for item in items:
                print '%s : %s' % (item[0], item[1])
                replaced_json = self.json_replace_recursive(replaced_json, item[0], item[1])
        # 返回修改好的配置json
        return replaced_json

3.测试

测试数据:命名为 data.config

# 注释行,将被忽略
####################################################################
##   注释
####################################################################
{
    "commonProduct":{
        "name":"普通商品汇总",
        "productList":[
            {
                "productId":"commonProduct_001",
                "productName":"矿泉水",
                "productPrice":"2.00"
            },
            {
                "productId":"commonProduct_002",
                "productName":"冰可乐",
                "productPrice":"3.50"
            }
        ]
    },
    "specialityProduct":{
        "name":"特色商品汇总",
        "productList":[
            {
                "productId":"specialityProduct_001",
                "productName":"椰子糖",
                "productPrice":"30.00"
            },
            {
                "productId":"specialityProduct_002",
                "productName":"芒果干",
                "productPrice":"35.00"
            }
        ]
    },
    "arryTest":["001"]
}

配置文件:命名为conf.ini

[data.config]
;price
commonProduct.desc=我是加入测试的
commonProduct.productList#0.productPrice=3.00
commonProduct.productList#1.productPrice=2.50

;id
specialityProduct.productList#0.productId=modifiedSpecialityProduct_001
;name
specialityProduct.productList#0.productName=椰子糖(无糖型)
;json arr modify and add
arryTest#0=modify_001
arryTest#1=add_001

; add key and val
specialityProduct.productList#0.addKey=addKey-val

; add whole object
;specialityProduct.productList#2.productId=addedSpecialityProduct_001
;specialityProduct.productList#2.productName=椰子糖(含糖型)
;specialityProduct.productList#2.productPrice=30.00
;specialityProduct.productList#2.additionalKey=additionalKey-val

specialityProduct.productList#3={"productId":"specialityProduct_002","productName":"榴莲糖","productPrice":"35.00"}

; add object 
objectTest={"name" : "ACME","shares" : 100,"price" : 542.23}

测试操作:


image.png

处理完成的文件:

# 注释行,将被忽略
####################################################################
##   注释
####################################################################
{
    "commonProduct": {
        "name": "普通商品汇总",
        "productList": [
            {
                "productId": "commonProduct_001",
                "productName": "矿泉水",
                "productPrice": 3.0
            },
            {
                "productId": "commonProduct_002",
                "productName": "冰可乐",
                "productPrice": 2.5
            }
        ],
        "desc": "我是加入测试的"
    },
    "specialityProduct": {
        "name": "特色商品汇总",
        "productList": [
            {
                "productId": "modifiedSpecialityProduct_001",
                "productName": "椰子糖(无糖型)",
                "productPrice": "30.00",
                "addKey": "addKey-val"
            },
            {
                "productId": "specialityProduct_002",
                "productName": "芒果干",
                "productPrice": "35.00"
            },
            {
                "productId": "specialityProduct_002",
                "productName": "榴莲糖",
                "productPrice": "35.00"
            }
        ]
    },
    "arryTest": [
        "modify_001",
        "add_001"
    ],
    "objectTest": {
        "name": "ACME",
        "shares": 100,
        "price": 542.23
    }
}

4. 总结

  • 未能实现移除已有键值对
  • 暂未对空JSON Arry增加JSON Object的异常情况处理
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容