DictionaryEx
Table of Contents
概要
- IDictionary<string, string> インターフェースを持ったオブジェクトを JSON 形式でシリアライズ/デシリアライズを行う。
- .NET Framework 3.5 未満の環境で、JavaScriptSerializer が使用できない場合での代替用。
- .NET Framework 2.0 対応。
- キー/値は共に string のみ。
- null はキーとして指定不可。
- 空文字列はキーとして指定可能。(JavaScriptSerializer 非互換)
- ダブルクォーテーション、シングルクォーテーションを含む場合はエスケープする必要がある。
- シリアライズ時のエンコーディングは UTF-16。
ソース
JSONSerializer.cs
/**
* @mainpage
* Dictionary を JSON 形式でシリアライズ/デシリアライズする。
*
* .NET Framework 3.5 未満の環境で、JavaScriptSerializer が使用できない場合での代替用。<br>
* .NET Framework 2.0 対応。<br>
* http://msdn.microsoft.com/ja-jp/library/system.web.script.serialization.javascriptserializer.aspx
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace TakeAsh.DictionaryEx
{
/// <summary>
/// IDictionary<string, string> インターフェースを持ったオブジェクトを JSON 形式でシリアライズ/デシリアライズを行う。
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>キー/値は共に string のみ。</item>
/// <item>null はキーとして指定不可。</item>
/// <item>空文字列はキーとして指定可能。(JavaScriptSerializer 非互換)</item>
/// <item>ダブルクォーテーション、シングルクォーテーションを含む場合はエスケープする必要がある。</item>
/// <item>シリアライズ時のエンコーディングは UTF-16。</item>
/// </list>
/// </remarks>
public class JSONSerializer
{
/// <summary>
/// シリアライズの際に削除する末尾の文字。
/// </summary>
private static char[] delChar = { ',', ' ', '\t', '\n', '\r', '\f', '\v' };
/// <summary>
/// オブジェクトを JSON 文字列へシリアライズする。
/// </summary>
/// <typeparam name="TInput">オブジェクトの型</typeparam>
/// <param name="dic">キー/値ペアが格納されているオブジェクト</param>
/// <returns>JSON文字列</returns>
static public string serialize<TInput>(TInput dic) where TInput : IDictionary<string, string>
{
string json = "";
if (dic != null)
{
foreach (string key in dic.Keys)
{
json += "\"" + _Util.encode(key) + "\": \"" + _Util.encode(dic[key]) + "\",\n";
}
}
return "{\n" + json.TrimEnd(delChar) + "\n}\n";
}
/// <summary>
/// オブジェクトを JSON 文字列へシリアライズする。
/// </summary>
/// <typeparam name="TInput">オブジェクトの型</typeparam>
/// <param name="dic">キー/値ペアが格納されているオブジェクト</param>
/// <returns>JSON文字列</returns>
public string Serialize<TInput>(TInput dic) where TInput : IDictionary<string, string>
{
return JSONSerializer.serialize(dic);
}
/// <summary>
/// JSON文字列をデシリアライズし TOutput 型のオブジェクトとして返す。
/// </summary>
/// <typeparam name="TOutput">オブジェクトの型</typeparam>
/// <param name="json">JSON文字列</param>
/// <returns>TOutput 型のオブジェクト</returns>
static public TOutput deserialize<TOutput>(string json) where TOutput : IDictionary<string, string>, new()
{
TOutput dic = new TOutput();
Regex reg = new Regex(@"(?<q1>['""])(?<key>[^'""]*)\k<q1>:\s*(?<q2>['""])(?<value>[^'""]*)\k<q2>");
MatchCollection mc = reg.Matches(_Util.subescape(json));
foreach (Match m in mc)
{
dic[Regex.Unescape(m.Groups["key"].Value)] = Regex.Unescape(m.Groups["value"].Value);
}
return dic;
}
/// <summary>
/// JSON文字列をデシリアライズし TOutput 型のオブジェクトとして返す。
/// </summary>
/// <typeparam name="TOutput">オブジェクトの型</typeparam>
/// <param name="json">JSON文字列</param>
/// <returns>TOutput 型のオブジェクト</returns>
public TOutput Deserialize<TOutput>(string json) where TOutput : IDictionary<string, string>, new()
{
return deserialize<TOutput>(json);
}
/// <summary>
/// キー/値ペアを追加する。
/// </summary>
/// <remarks>
/// 既存のキーと衝突する場合は上書きされる。
/// </remarks>
/// <typeparam name="TOutput">追加されるオブジェクトの型</typeparam>
/// <typeparam name="TInput">追加するオブジェクトの型</typeparam>
/// <param name="dicA">追加されるオブジェクト</param>
/// <param name="dicB">追加するオブジェクト</param>
/// <returns>追加されたオブジェクト</returns>
static public TOutput push<TOutput, TInput>(ref TOutput dicA, TInput dicB)
where TOutput : IDictionary<string, string>, new()
where TInput : IDictionary<string, string>
{
if (dicA == null)
{
dicA = new TOutput();
}
if (dicB != null)
{
foreach (string key in dicB.Keys)
{
dicA[key] = dicB[key];
}
}
return dicA;
}
/// <summary>
/// オブジェクトに格納されたキー/値ペアを追加する。
/// </summary>
/// <remarks>
/// 既存のキーと衝突する場合は上書きされる。
/// </remarks>
/// <typeparam name="TOutput">追加されるオブジェクトの型</typeparam>
/// <typeparam name="TInput">追加するオブジェクトの型</typeparam>
/// <param name="dicA">追加されるオブジェクト</param>
/// <param name="dicB">追加するオブジェクト</param>
/// <returns>追加されたオブジェクト</returns>
public TOutput Push<TOutput, TInput>(ref TOutput dicA, TInput dicB)
where TOutput : IDictionary<string, string>, new()
where TInput : IDictionary<string, string>
{
return push(ref dicA, dicB);
}
/// <summary>
/// JSON 文字列を解釈しキー/値ペアを追加する。
/// </summary>
/// <remarks>
/// 既存のキーと衝突する場合は上書きされる。
/// </remarks>
/// <typeparam name="TOutput">追加されるオブジェクトの型</typeparam>
/// <param name="dic">追加されるオブジェクト</param>
/// <param name="json">追加する JSON 文字列</param>
/// <returns>追加されたオブジェクト</returns>
static public TOutput push<TOutput>(ref TOutput dic, string json) where TOutput : IDictionary<string, string>, new()
{
return push(ref dic, deserialize<TOutput>(json));
}
/// <summary>
/// JSON 文字列を解釈しキー/値ペアを追加する。
/// </summary>
/// <remarks>
/// 既存のキーと衝突する場合は上書きされる。
/// </remarks>
/// <typeparam name="TOutput">追加されるオブジェクトの型</typeparam>
/// <param name="dic">追加されるオブジェクト</param>
/// <param name="json">追加する JSON 文字列</param>
/// <returns>追加されたオブジェクト</returns>
public TOutput Push<TOutput>(ref TOutput dic, string json) where TOutput : IDictionary<string, string>, new()
{
return push(ref dic, deserialize<TOutput>(json));
}
}
}
// EOF
_Util.cs
using System;
using System.Text;
using System.Text.RegularExpressions;
namespace TakeAsh.DictionaryEx
{
internal class _Util
{
/// <summary>
/// Shift_JIS 範囲に対応する文字クラス
/// </summary>
/// <remarks>
/// この範囲にマッチする文字はエスケープしない。
/// </remarks>
private static Regex regKanji = new Regex(
@"["
//+ @"\p{IsBasicLatin}" // 制御文字を含むのでマッチ範囲から外す
+ @"\p{IsLatin-1Supplement}"
+ @"\p{IsGreek}"
+ @"\p{IsCyrillic}"
+ @"\p{IsGeneralPunctuation}"
+ @"\p{IsLetterlikeSymbols}"
+ @"\p{IsNumberForms}"
+ @"\p{IsArrows}"
+ @"\p{IsMathematicalOperators}"
+ @"\p{IsMiscellaneousTechnical}"
+ @"\p{IsEnclosedAlphanumerics}"
+ @"\p{IsBoxDrawing}"
+ @"\p{IsGeometricShapes}"
+ @"\p{IsMiscellaneousSymbols}"
+ @"\p{IsCJKSymbolsandPunctuation}"
+ @"\p{IsHiragana}"
+ @"\p{IsKatakana}"
+ @"\p{IsEnclosedCJKLettersandMonths}"
+ @"\p{IsCJKCompatibility}"
+ @"\p{IsCJKUnifiedIdeographs}"
+ @"\p{IsCJKCompatibilityIdeographs}"
+ @"\p{IsHalfwidthandFullwidthForms}"
+ @"]"
);
/// <summary>
/// 引用符のエスケープ形式を変換する。
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>\\" → \\u0022</item>
/// <item>\\' → \\u0027</item>
/// </list>
/// </remarks>
/// <param name="str">文字列</param>
/// <returns>エスケープされた文字列</returns>
public static string subescape(string str)
{
Regex regQuote = new Regex(@"\\(?<char>[""'])");
return regQuote.Replace(str, new MatchEvaluator(toHex));
}
/// <summary>
/// 記号等の文字をエスケープする。
/// </summary>
/// <remarks>
/// 文字クラス http://msdn.microsoft.com/ja-jp/library/20bw873z
/// </remarks>
/// <param name="str">文字列</param>
/// <returns>エスケープされた文字列</returns>
public static string encode(string str)
{
Regex regEscape = new Regex(
@"(?<char>[^-0-9A-Za-z\s\[\]\{\}"
+ Regex.Escape("!#$%&()*+,./:;=?@^_`|~")
+ @"])"
);
return regEscape.Replace(str, new MatchEvaluator(toHex));
}
/// <summary>
/// 文字を16進化する。
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="m">文字(グループ名"char")</param>
/// <returns>16進化した文字</returns>
public static string toHex(Match m)
{
string c1 = m.Groups["char"].Value;
int code = (int)c1[0];
bool f1 = !regKanji.Match(c1).Success;
bool f2 = !IsShiftJIS(c1);
string ret = (f1 || f2) ? String.Format(@"\u{0:x4}", code) : c1;
return ret;
}
/// <summary>
/// Shift_JIS 範囲内の文字のみかどうかをチェックして、その結果を返す。
/// </summary>
/// <remarks>
/// http://acha-ya.cocolog-nifty.com/blog/2010/12/unicode-ef79.html
/// </remarks>
/// <param name="checkString">チェック対象の文字列</param>
/// <returns>
/// <list type="bullet">
/// <item>true: 全ての文字は Shift_JIS 範囲内である</item>
/// <item>false: Shift_JIS 範囲外の文字が含まれている</item>
/// </list>
/// </returns>
public static bool IsShiftJIS(string checkString)
{
byte[] translateBuffer = Encoding.GetEncoding("shift_jis").GetBytes(checkString);
string translateString = Encoding.GetEncoding("shift_jis").GetString(translateBuffer);
return (checkString == translateString.ToString());
}
}
}
// EOF
JSONableDictionary.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace TakeAsh.DictionaryEx
{
/// <summary>
/// JSON 形式としてシリアライズ/デシリアライズできる Dictionary
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>シリアライズに際して、キーの順番はソートされない。</item>
/// <item>キー/値は共に string のみ。</item>
/// <item>null はキーとして指定不可。</item>
/// <item>空文字列はキーとして指定可能。(JavaScriptSerializer 非互換)</item>
/// <item>ダブルクォーテーション、シングルクォーテーションを含む場合はエスケープする必要がある。</item>
/// <item>シリアライズ時のエンコーディングは UTF-16。</item>
/// </list>
/// </remarks>
public class JSONableDictionary : Dictionary<string,string>
{
/// <summary>
/// コンストラクタ
/// </summary>
public JSONableDictionary()
: base()
{
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="dic">キー/値ペア</param>
public JSONableDictionary(IDictionary<string, string> dic)
: base(dic)
{
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="json">JSON文字列</param>
public JSONableDictionary(string json)
{
Dictionary<string, string> dic = JSONSerializer.deserialize<Dictionary<string, string>>(json);
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
}
/// <summary>
/// JSON文字列をデシリアライズし自身にセットする。
/// </summary>
/// <remarks>
/// 既存の要素は消去される。
/// </remarks>
/// <param name="json">JSON文字列</param>
/// <returns>this</returns>
public JSONableDictionary Deserialize(string json)
{
this.Clear();
Dictionary<string, string> dic = JSONSerializer.deserialize<Dictionary<string, string>>(json);
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
return this;
}
/// <summary>
/// JSON文字列としてシリアライズする。
/// </summary>
/// <returns>JSON文字列</returns>
public string Serialize()
{
return JSONSerializer.serialize(this);
}
/// <summary>
/// キー/値ペアを追加する。
/// </summary>
/// <remarks>
/// 既存のキーと重複する場合は上書きされる。
/// </remarks>
/// <param name="dic">追加するキー/値ペア</param>
/// <returns>this</returns>
public JSONableDictionary Push<T>(T dic) where T : IDictionary<string, string>
{
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
return this;
}
/// <summary>
/// json文字列をデシリアライズし追加する。
/// </summary>
/// <param name="json">JSON文字列</param>
/// <returns>this</returns>
public JSONableDictionary Push(string json)
{
JSONableDictionary dic = Deserialize(json);
Push(dic);
return this;
}
/// <summary>
/// 現在のオブジェクトを表す文字列を返す。
/// </summary>
/// <returns>現在のオブジェクトを表す文字列</returns>
public new string ToString()
{
return Serialize();
}
/// <summary>
/// オブジェクト同士を比較する。
/// </summary>
/// <param name="other">比較対象オブジェクト</param>
/// <returns>
/// <list type="bullet">
/// <item>true: 値が一致</item>
/// <item>false:
/// <list type="bullet">
/// <item>比較対象オブジェクトが null</item>
/// <item>比較対象オブジェクトの型が異なる</item>
/// <item>値が不一致</item>
/// </list>
/// </item>
/// </list>
/// </returns>
public override bool Equals(object other)
{
if (other == null)
{
return false;
}
JSONableDictionary p = other as JSONableDictionary;
if ((Object)p == null)
{
return false;
}
return this.Serialize() == p.Serialize();
}
/// <summary>
/// ハッシュ コードを返す。
/// </summary>
/// <returns>現在のハッシュ コード</returns>
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}
// EOF
JSONableSortedDictionary.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace TakeAsh.DictionaryEx
{
/// <summary>
/// JSON 形式としてシリアライズ/デシリアライズできる SortedDictionary
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>シリアライズに際して、キーの順番でソートされる。</item>
/// <item>キー/値は共に string のみ。</item>
/// <item>null はキーとして指定不可。</item>
/// <item>空文字列はキーとして指定可能。(JavaScriptSerializer 非互換)</item>
/// <item>ダブルクォーテーション、シングルクォーテーションを含む場合はエスケープする必要がある。</item>
/// <item>シリアライズ時のエンコーディングは UTF-16。</item>
/// </list>
/// </remarks>
public class JSONableSortedDictionary : SortedDictionary<string, string>
{
/// <summary>
/// コンストラクタ
/// </summary>
public JSONableSortedDictionary()
: base()
{
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="dic">キー/値ペア</param>
public JSONableSortedDictionary(IDictionary<string, string> dic)
: base(dic)
{
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="json">JSON文字列</param>
public JSONableSortedDictionary(string json)
{
Dictionary<string, string> dic = JSONSerializer.deserialize<Dictionary<string, string>>(json);
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
}
/// <summary>
/// JSON文字列をデシリアライズし自身にセットする。
/// </summary>
/// <remarks>
/// 既存の要素は消去される。
/// </remarks>
/// <param name="json">JSON文字列</param>
/// <returns>this</returns>
public JSONableSortedDictionary Deserialize(string json)
{
this.Clear();
Dictionary<string, string> dic = JSONSerializer.deserialize<Dictionary<string, string>>(json);
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
return this;
}
/// <summary>
/// JSON文字列としてシリアライズする。
/// </summary>
/// <returns>JSON文字列</returns>
public string Serialize()
{
return JSONSerializer.serialize(this);
}
/// <summary>
/// キー/値ペアを追加する。
/// </summary>
/// <remarks>
/// 既存のキーと重複する場合は上書きされる。
/// </remarks>
/// <param name="dic">追加するキー/値ペア</param>
/// <returns>this</returns>
public JSONableSortedDictionary Push<T>(T dic) where T : IDictionary<string, string>
{
foreach (string key in dic.Keys)
{
this[key] = dic[key];
}
return this;
}
/// <summary>
/// json文字列をデシリアライズし追加する。
/// </summary>
/// <param name="json">JSON文字列</param>
/// <returns>this</returns>
public JSONableSortedDictionary Push(string json)
{
JSONableSortedDictionary dic = Deserialize(json);
Push(dic);
return this;
}
/// <summary>
/// 現在のオブジェクトを表す文字列を返す。
/// </summary>
/// <returns>現在のオブジェクトを表す文字列</returns>
public new string ToString()
{
return Serialize();
}
/// <summary>
/// オブジェクト同士を比較する。
/// </summary>
/// <param name="other">比較対象オブジェクト</param>
/// <returns>
/// <list type="bullet">
/// <item>true: 値が一致</item>
/// <item>false:
/// <list type="bullet">
/// <item>比較対象オブジェクトが null</item>
/// <item>比較対象オブジェクトの型が異なる</item>
/// <item>値が不一致</item>
/// </list>
/// </item>
/// </list>
/// </returns>
public override bool Equals(object other)
{
if (other == null)
{
return false;
}
JSONableSortedDictionary p = other as JSONableSortedDictionary;
if ((Object)p == null)
{
return false;
}
return this.Serialize() == p.Serialize();
}
/// <summary>
/// ハッシュ コードを返す。
/// </summary>
/// <returns>現在のハッシュ コード</returns>
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}
// EOF
Program.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Script.Serialization;
using TakeAsh.DictionaryEx;
namespace Tester
{
class Program
{
static void Main(string[] args)
{
JavaScriptSerializer jss = new JavaScriptSerializer();
JSONSerializer tjs = new JSONSerializer();
Dictionary<string, string> dic1 = new Dictionary<string, string>();
dic1["A"] = "AAA";
dic1["a"] = "aaa";
dic1["123"] = "abc";
dic1["あ"] = "あ";
dic1[""] = "BlankKey";
dic1["Escape"] = "\\\"\'\t\n\\\"\'\t\n";
dic1["Unicode"] = "\u9AD8\u9ad9 \u5D0E\ufa11 \u5409\uD842\uDFB7 \u53F1\uD842\uDF9F \u5265\u525D \u586B\u5861 \u982C\u9830";
dic1["Latin-1Supplement"] = "§¨°±´¶×÷";
dic1["Greek"] = "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω";
dic1["Cyrillic"] = "ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"
+"абвгдежзийклмнопрстуфхцчшщъыьэюяё";
dic1["GeneralPunctuation"] = "‐―‘’“”†‡‥…‰′″※";
dic1["LetterlikeSymbols"] = "℃№℡Å";
dic1["NumberForms"] = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ";
dic1["Arrows"] = "←↑→↓⇒⇔";
dic1["MathematicalOperators"] = "∀∂∃∇∈∋∑√∝∞∟∠∥∧∨∩∪∫∬∮∴∵∽≒≠≡≦≧≪≫⊂⊃⊆⊇⊥⊿";
dic1["MiscellaneousTechnical"] = "⌒";
dic1["EnclosedAlphanumerics"] = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳";
dic1["BoxDrawing"] = "─━│┃┌┏┐┓└┗┘┛├┝┠┣┤┥┨┫┬┯┰┳┴┷┸┻┼┿╂╋";
dic1["GeometricShapes"] = "■□▲△▼▽◆◇○◎●◯";
dic1["MiscellaneousSymbols"] = "★☆♀♂♪♭♯";
dic1["CJKSymbolsandPunctuation"] = " 、。〃々〆〇〈〉《》「」『』【】〒〓〔〕〝〟";
dic1["Hiragana"] = "あかさたなはまやらわをん";
dic1["Katakana"] = "アカサタナハマヤラワヲン";
dic1["EnclosedCJKLettersandMonths"] = "㈱㈲㈹㊤㊥㊦㊧㊨";
dic1["CJKCompatibility"] = "㌃㌍㌔㌘㌢㌣㌦㌧㌫㌶㌻㍉㍊㍍㍑㍗㍻㍼㍽㍾㎎㎏㎜㎝㎞㎡㏄㏍";
dic1["CJKUnifiedIdeographs"] = "一丁七万丈三上下不与丐丑且丕世丗丘丙丞両";
dic1["CJKCompatibilityIdeographs"] = "朗隆﨎﨏塚﨑晴﨓﨔凞猪益礼神祥福靖精羽﨟蘒﨡諸﨣﨤逸都﨧﨨﨩飯飼館鶴";
dic1["HalfwidthandFullwidthForms"] = "!"#$%&'()*+,-./0123456789:;<=>?"
+ "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_"
+ "`abcdefghijklmnopqrstuvwxyz{|}~。"
+ "「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚¢£¬ ̄¦¥";
for (int high = 0; high < 8; ++high)
{
string buf = "";
for (int low = 0; low < 16; ++low)
{
buf += ((char)(high * 16 + low)).ToString();
}
dic1[String.Format("{0:x2}", high)] = buf;
}
string tjsJSON = tjs.Serialize(dic1);
Console.WriteLine("TakeAsh/JSONSerializer dic1\n{0}", tjsJSON);
string jssJSON = jss.Serialize(dic1) + "\n";
Console.WriteLine("System/JavaScriptSerializer dic1\n{0}", jssJSON);
JSONableSortedDictionary dic2 = new JSONableSortedDictionary();
dic2.Deserialize(jssJSON);
Console.WriteLine("Syste/Serialize -> TakeAsh/Deserialize(SortedDictionary))\n{0}", tjs.Serialize(dic2));
SortedDictionary<string, string> dic3 = null;
try
{
dic3 = jss.Deserialize<SortedDictionary<string, string>>(tjsJSON);
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}\n", e);
}
tjsJSON = Regex.Replace(tjsJSON, "\"\":\\s*\"[^\"]*\",", ""); // 空文字列なキーを削除
dic3 = jss.Deserialize<SortedDictionary<string, string>>(tjsJSON);
Console.WriteLine("TakeAsh/Serialize -> Syste/Deserialize(SortedDictionary))\n{0}", tjs.Serialize(dic3));
Console.WriteLine("eq? (空文字列キーなし): {0}", dic2.Equals(new JSONableSortedDictionary(dic3)));
dic3[""] = "BlankKey";
Console.WriteLine("eq? (空文字列キー追加): {0}", dic2.Equals(new JSONableSortedDictionary(dic3)));
}
}
}
//EOF