pythonではjsonモジュールはパッケージ同梱ですが、yamlモジュールはサードパーティ製です。
現在作成しているプログラムでは、環境の違いを吸収するため
- もしyamlモジュールがあり、yamlのconfigファイルがあればそれを読む。
- ダメならjsonでの読み込みを試みる。
- それでもだめなら例外を挙げる
という挙動を採用しています。
yamlとjsonは似たインターフェイスを持っているので、以下のように全く同じように扱ったのですが、
try:
import yaml
with open("config.yaml") as y:
config = yaml.load("config.yaml")
except ImportError:
import json
try:
with open("config.json") as j:
config = json.load(j)
except AttributeError:
logger.error("please set config file in json or yaml !!")
raise
# 以下でconfigの値を使用する…
ここに落とし穴がありました。
yamlの場合、keyの型を自動的に推測して読み込んでくれるのですが、jsonは必ずstr
にします。
つまりコンフィグファイルが以下のような内容だった場合
config.yaml
1: "hoge"
key: "fuga"
config.json
{
"1": "hoge",
"key": "fuga"
}
いずれの場合もconfig["key"]
で"fuga"
にアクセスできますが、"hoge"
の方はjsonならばconfig["1"]
, yamlならばconfig[1]
でアクセスしなくてはなりません。
これを避けるにはjson.load()
時にフックとして関数を与えます。
def jsonkeys_to_int_always(x): # keyの型をすべてintにできるときはこちらでOK
if isinstance(x, dict):
return {int(k):v for k,v in x.items()}
return x
def jsonkey_to_int_when_possible(x): # yamlと同様の挙動をさせたいときはこちら
d = {}
if isinstance(x, dict):
for k,v in x.items():
try:
k = int(k)
except ValueError:
pass
d[k] = v
return d
config = json.load("config.json", object_hook=jsonkeys_to_int_when_possible)
yamlの中で値を指定することで、違いを吸収することもできる様ですが、詳しくは知りません。