配置文件(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的异常情况处理
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容