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

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

作者: 西瓜雪梨桔子汁 | 来源:发表于2019-05-19 18:31 被阅读0次

    实际使用中发现,在进行配置时,对增加键值的需要还是很旺盛的,比如一个账号列表配置要新增账号、一个配置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的异常情况处理

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/jquqzqtx.html