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