實(shí)際使用中發(fā)現(xiàn),在進(jìn)行配置時(shí)霍弹,對(duì)增加鍵值的需要還是很旺盛的,比如一個(gè)賬號(hào)列表配置要新增賬號(hào)光涂、一個(gè)配置JSON Array增加完整的JSON配置塊等等庞萍,鑒于此拧烦,更新了工具忘闻,滿足此類需求。
1.分析
增加配置涉及的類型基本有這么幾種:
- JSON Object增加新的鍵值對(duì)恋博,值為普通字符串
- JSON Array內(nèi)部是一個(gè)個(gè)字符串齐佳,如:['u1001','U1002']
- JSON Object增加新的鍵值對(duì),值是復(fù)雜的JSON Obect或者JSON Array
- JSON Array內(nèi)部是一個(gè)個(gè)JSON Object债沮,如:
[
{
"productId":"commonProduct_001",
"productName":"礦泉水",
"productPrice":"2.00"
},
{
"productId":"commonProduct_002",
"productName":"冰可樂",
"productPrice":"3.50"
}
]
前2種很好解決炼吴,對(duì)一個(gè)JSON對(duì)象判斷鍵在不在,如果在就是替換疫衩、不在就是增加硅蹦,且增加都是字符串類型,直接替換原值就行闷煤,后面2種就顯得麻煩些童芹。
2.實(shí)現(xiàn)
在之前文章實(shí)現(xiàn)的基礎(chǔ)上,重點(diǎn)改造點(diǎn)主要有以下幾方面:
- 對(duì)替換JSON Object部分改造鲤拿,替換某個(gè)key前需要先判斷它在不在當(dāng)前JSON中
- 對(duì)替換JSON Array部分改造假褪,替換某個(gè)index指向的array時(shí)先判斷array的長度是否大于index,如果大于則是替換近顷,反之是增加生音。
- 增加邏輯中對(duì)待增加的值進(jìn)行JSON合法性檢測(cè),是非法的JSON結(jié)構(gòu)以普通字符串添加窒升,否則以解析完成的字典格式添加缀遍。
2.1 檢測(cè)字符串是否為JSON
基本原理是利用json.loads()嘗試解析字符串,如果不是合法json格式饱须,則會(huì)拋出ValueError異常域醇。
def is_json(json_str):
'''
判讀是否合法的JSON字符串
'''
try:
json.loads(json_str)
except ValueError:
return False
return True
測(cè)試的結(jié)果如下:
2.2 替換/增加字符串邏輯
涉及代碼片段:
def json_replace_object(self, conf, key, val):
'''
對(duì)json的指定key替換值
'''
if(not conf.has_key(key)):
print(u'增加key為:%s、val為:%s鍵值對(duì)' % (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
處理邏輯:
- 檢測(cè)JSON/字典是否含有指定的key,沒有則添加歹苦、有則替換青伤。不過對(duì)于python添加、替換操作可合二為一殴瘦。
- 判斷待參數(shù)val是否為合法的JSON字符串狠角,是則轉(zhuǎn)為JSON對(duì)象在添加或替換,否則作為原始字符串添加或替換蚪腋。
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個(gè)元素替換為:%s' % (conf_arrary, index, val))
conf_arrary[index] = val
# 返回
return conf_arrary
對(duì)JSON Array處理丰歌,關(guān)注參數(shù)index,它表示需要替換或者增加的JSON Array索引值屉凯,所以大致邏輯是:
- 如果索引值index小于JSON Array長度立帖,只是替換,且替換的值為普通字符串(暫時(shí)不支持這樣替換JSON Array中完整的JSON對(duì)象的操作悠砚,如果需要這里也是判斷參數(shù)值是否為JSON字符串就可以)
- 如果索引值index大于或等于JSON Array長度晓勇,就是需要增加array元素的情況了,在依據(jù)值是否為合法JSON字符串處理:
- 是普通字符串灌旧,以字符串方式增加
- 是合法JSON字符串绑咱,解析為字典,添加到目標(biāo)字典中
2.4 JSON Array的子JSON Object新增或替換
這種事比較復(fù)雜的一種情形枢泰,舉幾個(gè)例子:
- 新增普通鍵值對(duì)
假設(shè)文章開頭JSON例子要在JSON Array的第一個(gè)元素新增描融,形成下面的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"
}
]
相關(guān)的代碼片段:
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個(gè)copy
conf_arrary.insert(index,conf_arrary[0])
real_conf = conf_arrary[index]
else:
real_conf = conf_arrary[index]
# 對(duì)待替換的配置繼續(xù)遞歸處理
replaced_conf = self.json_replace_recursive(real_conf, key_pattern[key_pattern.index('.')+1:], val)
# 修改好的值替換掉原本的這個(gè)index的array中的值
if(len(conf_arrary) <= index):
conf_arrary.insert(index,replaced_conf)
else:
conf_arrary[index] = replaced_conf
# 再將這個(gè)array賦值回原本json的這個(gè)key的部分衡蚂,達(dá)到改變配置效果
conf[real_key] = conf_arrary
# 返回調(diào)用者的是對(duì)原始json替換后的
return conf
相關(guān)邏輯都在判斷key按照“.”拆分后能拆分列表大小窿克,只能拆分為1說明到達(dá)目標(biāo)key那一級(jí),反之需要遞歸處理毛甲,所以邏輯都在else處理的遞歸邏輯中年叮。
按照之前定義,只有“#”指定一個(gè)JSON Array的索引值丽啡,對(duì)解析到key包含“#”谋右,我們就知道是要進(jìn)行元素替換或者新增了。
跟上面類似补箍,這里邏輯是:
- 索引值index小于array長度改执,替換操作,只是需要遞歸替換坑雅,因?yàn)閗ey還含有“.”
- 索引值index大于或等于array長度辈挂,增加操作:先把Array的地1個(gè)部分copy到array的index位置,再作為參數(shù)交給遞歸方法處理裹粤;處理完的Array终蒂,在賦值回原始的array對(duì)應(yīng)的key,實(shí)現(xiàn)替換。
這里這個(gè)copy操作是有bug的拇泣,如果本身是空的array噪叙,就會(huì)出問題,copy不到值霉翔,且遞歸時(shí)檢測(cè)是否含有子key也會(huì)出錯(cuò)睁蕾。
2.5 完整替換邏輯
如下是替換或新增的邏輯:
def is_json(json_str):
'''
判讀是否合法的JSON字符串
'''
try:
json.loads(json_str)
except ValueError:
return False
return True
class ContentModifier(object):
'''
配置內(nèi)容修改器,依據(jù)配置項(xiàng)的key-val對(duì)债朵,進(jìn)行配置文件的修改
'''
def __init__(self, conf_paraser):
'''
初始化方法
'''
self.conf_paraser = conf_paraser
def json_replace_object(self, conf, key, val):
'''
對(duì)json的指定key替換值
'''
if(not conf.has_key(key)):
print(u'增加key為:%s子眶、val為:%s鍵值對(duì)' % (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個(gè)元素替換為:%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為例序芦,表示:
待修改的值在一級(jí)keyCsvFileExportToCoreService的值中臭杰,且它是array,#0指明要修改的在array的第一個(gè)
待修改的值在第一個(gè)array的key為exportRules中谚中,這個(gè)exportRules的值也是array渴杆,#0需要修改的指明要修改的在array的第一個(gè)
待修改的值在第一個(gè)array的fileExportRules指定值中,此為json對(duì)象
待修改的值在json對(duì)象的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個(gè)copy
conf_arrary.insert(index,conf_arrary[0])
real_conf = conf_arrary[index]
else:
real_conf = conf_arrary[index]
# 對(duì)待替換的配置繼續(xù)遞歸處理
replaced_conf = self.json_replace_recursive(real_conf, key_pattern[key_pattern.index('.')+1:], val)
# 修改好的值替換掉原本的這個(gè)index的array中的值
if(len(conf_arrary) <= index):
conf_arrary.insert(index,replaced_conf)
else:
conf_arrary[index] = replaced_conf
# 再將這個(gè)array賦值回原本json的這個(gè)key的部分将塑,達(dá)到改變配置效果
conf[real_key] = conf_arrary
# 返回調(diào)用者的是對(duì)原始json替換后的
return conf
else:
# 不是array類型,直接取出值進(jìn)行遞歸替換
# 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段配置,對(duì)content進(jìn)行修改
'''
#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)
# 替換所有需要的項(xiàng)
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.測(cè)試
測(cè)試數(shù)據(jù):命名為 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=我是加入測(cè)試的
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}
測(cè)試操作:
處理完成的文件:
# 注釋行来吩,將被忽略
####################################################################
## 注釋
####################################################################
{
"commonProduct": {
"name": "普通商品匯總",
"productList": [
{
"productId": "commonProduct_001",
"productName": "礦泉水",
"productPrice": 3.0
},
{
"productId": "commonProduct_002",
"productName": "冰可樂",
"productPrice": 2.5
}
],
"desc": "我是加入測(cè)試的"
},
"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. 總結(jié)
- 未能實(shí)現(xiàn)移除已有鍵值對(duì)
- 暫未對(duì)空J(rèn)SON Arry增加JSON Object的異常情況處理