2014年11月27日

dynamicJSONで日本語のキーを持つ連想配列をforeachするお話

C#でJSONを扱う際、様々な方法がありますが、その中にdynamicJSONというライブラリを利用する方法があります。

.NET 4.0以降に存在するdynamicを利用して、かなり使い勝手の良いライブラリなのですが、日本語のキーのオブジェクトをforeachで回すと、"item"というキーになってしまうバグを見つけました。
dynamicJSONはオープンソースなのですが、開発が何年も前からストップしており、プルリクしたくても出来ない状況にあるので、バグの詳細と修正方法をここに記しておきます。

1.どういう時にバグるの?
以下の様なコードがあるとします。

var jsonStr = @"{""key"" : ""がおっ"",""日本語のキー"" : ""めう!!""}";
var jsonObj = DynamicJson.Parse(jsonStr);
foreach (KeyValuePair<string, object> pair in jsonObj)
{
Console.WriteLine(@"jsonObj[""{0}""] = ""{1}""", pair.Key, pair.Value);
}

これをdynamicJSONを用いてパースした時、期待する挙動はこうですよね?

jsonObj["key"] = "がおっ"
jsonObj["日本語のキー"] = "めう!!"


が、現実はこうです。

jsonObj["key"] = "がおっ"
jsonObj["item"] = "めう!!"





・・・(^ω^)は?w



itemってなんだよ、itemって!!!マジでいみわかんねーぞwwww
ってずっと悩んでいましたが、ちょっとだけ原因分かりました(詳しくは分かっていない)

2.なんでバグるの?
dynamicJSONは、内部処理で、文字列⇒XMLオブジェクト⇒dynamicJSONオブジェクトのように、途中でXML構造に変換するようです。つまりXElementをごにょるらしい。

で、さっきの例のやつのXMLにした段階での出力はこんな感じ。

<root type="object">
<key type="string">がおっ</key>
<a:item xmlns:a="item" item="日本語のキー" type="string">めう!!</a:item>
</root>


どう考えてもバグっぽい挙動です本当にありがとうございました

3.どうやって直すの?
と、いうわけで勘が良い方はお気づきだと思いますが、DynamicJSONでは、このエレメントの、要素名をKey、中身をValueとしてるので、前者は問題ないのですが、後者では要素名ではなく、itemとかいう名前の属性にKeyが入るので、日本語のKeyを指定した時におかしくなった、というわけですねっ

とはいえ、日本語タグを入れるバグ、この.NETのパーサー挙動の仕様らしいです。ならばしょうがないな。

と、いうわけで該当ソースコードにクローズあーっぷ!


// Deserialize or foreach(IEnumerable)
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (binder.Type == typeof(IEnumerable) || binder.Type == typeof(object[]))
{
var ie = (IsArray)
? xml.Elements().Select(x => ToValue(x))
: xml.Elements().Select(x => (dynamic)new KeyValuePair<string, object>(x.Name.LocalName, ToValue(x)));
result = (binder.Type == typeof(object[])) ? ie.ToArray() : ie;
}
else
{
result = Deserialize(binder.Type);
}
return true;
}

はいここですね〜〜〜〜

早速修正します(とは言え数行だけ

// Deserialize or foreach(IEnumerable)
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (binder.Type == typeof(IEnumerable) || binder.Type == typeof(object[]))
{
var ie = (IsArray)
? xml.Elements().Select(x => ToValue(x))
// : xml.Elements().Select(x => (dynamic)new KeyValuePair<string, object>(x.Name.LocalName, ToValue(x)));
: xml.Elements().Select(x =>{
var name = x.Name.LocalName;
var item = x.Attribute("item");
if ((name == "item") && (item != null))
{
name = item.Value;
}
return (dynamic)new KeyValuePair<string, object>(name, ToValue(x));
});
result = (binder.Type == typeof(object[])) ? ie.ToArray() : ie;
}
else
{
result = Deserialize(binder.Type);
}
return true;
}


これだけ。
やってることは、要素名をみて、もしそれがitemだったらitem="〇〇"っていう属性を探してきて、その属性に充てられてる値を、Keyとする、ってだけの処理。

これで、日本語のキーを指定できるようになりました。めでたしめでたし。
posted by がお at 01:35| Comment(1) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
DynamicJsonと連想配列の動きが期待通りでなかった所、
ググってここにたどり着きました。
日本語タグを使用していた訳ではないのですが、
こちらのコードを試してみたら期待通りの動作になりました。
非常に助かりました。

ありがとうございます。
Posted by かめ at 2016年08月06日 22:55
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。