ぷろぐらみんぐ帳

C#とかJavaScriptとか

連想配列のJSONをパースする

JSONを扱っているとたまにこのような連想配列の文字列に出くわすことがある。

{
  "fruits": {
    "1": {
      "key": 1,
      "value": [
        "apple",
        "りんご",
        150,
        "青森県"
      ]
    },
    "2": {
      "key": 2,
      "value": [
        "melon",
        "メロン",
        600,
        "茨城県"
      ]
    },
    "3": {
      "key": 3,
      "value": [
        "watermelon",
        "スイカ",
        1400,
        "熊本県"
      ]
    }
  }
}

valueの中に文字列だろうが数値だろうが放り込まれている気持ち悪いパターン。C#のように型付けありきの言語だと強烈に違和感があるが、JavaScriptの仕様的にこれはまかり通ってしまう。 配列の中に何でもかんでもぶち込まなくても、実は連想配列がある場合すらライブラリによってはパースに苦労することがある

DynamicJSONの場合

JSONを扱っていると大変お世話になるライブラリだが(軽いし!)、連想配列はなぜかうまくいかない。例えば、このようにテンプレートクラスを定義してパースすると、

using System.IO;
using Codeplex.Data;

class Program
{
    static void Main(string[] args)
    {
        var jsonStr = File.ReadAllText("json1.json");//JSON文字列を読み込む

        var jsonObj = DynamicJson.Parse(jsonStr);//DynamicJSONで読み込み
        RootJson fruits = jsonObj.Deserialize<RootJson>();

        foreach(var f in fruits.fruits)
        {
            Console.WriteLine(f.ToString());
        }
        //実行結果
        //続行するには何かキーを押してください . . .
    }
}

public class RootJson
{
    public Dictionary<int, Fruit> fruits { get; set; }

    public class Fruit
    {
        public int key { get; set; }
        public object[] value { get; set; }

        public override string ToString()
        {
            return "key : " + key + ", value : [" + string.Join(", ", value) + "]"; 
        }
    }
}

なにも表示されない……!?ブレークポイントで止めると…明らかにパースに失敗してるよね。 f:id:enjyuu:20170707221505p:plain うまい方法あるのかもしれないがちょっとこれはいただけない。ちなみにfruitsの定義をDictionary<string, Fruit>に変えてもダメ。

Json.NETの場合

これはすんなり行く。DynamicJSONと同じテンプレートクラスを使っても、

using System.IO;
using Newtonsoft.Json;

class Program
{
    static void Main(string[] args)
    {
        var jsonStr = File.ReadAllText("json1.json");//JSON文字列を読み込む

        var fruits = JsonConvert.DeserializeObject<RootJson>(jsonStr);

        foreach(var f in fruits.fruits)
        {
            Console.WriteLine(f.ToString());
        }
        /*
        実行結果
        [1, key : 1, value : [apple, りんご, 150, 青森県]]
        [2, key : 2, value : [melon, メロン, 600, 茨城県]]
        [3, key : 3, value : [watermelon, スイカ, 1400, 熊本県]]
        続行するには何かキーを押してください . . .
         */
    }
}

望んだ通りの結果に。

DataContractJsonSerializerの場合

ちなみに.NET付属のDataContractJsonSerializer(System.Runtime.Serialization.Json)を使う場合は例外が発生

using System.IO;
using System.Runtime.Serialization.Json;

class Program
{
    static void Main(string[] args)
    {
        var serializer = new DataContractJsonSerializer(typeof(RootJson));
        RootJson fruits;
        using(var sr = new StreamReader("json1.json", Encoding.GetEncoding("UTF-8")))
        {
            fruits = serializer.ReadObject(sr.BaseStream) as RootJson;
        }

        foreach(var f in fruits.fruits)
        {
            Console.WriteLine(f.ToString());
        }
    }
}

ハンドルされていない例外: System.Runtime.Serialization.SerializationException: オブジェクト 型 RootJson の のシリアル化を解除しているときにエラーが発生しました 。予期しない文字 ‘i’ が見つかりました。 —> System.Xml.XmlException: 予期しない 文字 ‘i’ が見つかりました。 場所 System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader read er, XmlException exception) 場所 System.Runtime.Serialization.Json.XmlJsonReader.ReadAttributes() 場所 System.Runtime.Serialization.Json.XmlJsonReader.ReadNonExistentElementNa me(StringHandleConstStringType elementName) 場所 System.Runtime.Serialization.Json.XmlJsonReader.Read() 場所 System.Xml.XmlBaseReader.IsStartElement() 場所 System.Xml.XmlBaseReader.IsStartElement(XmlDictionaryString localName, X mlDictionaryString namespaceUri) 場所 System.Runtime.Serialization.XmlReaderDelegator.IsStartElement(XmlDictio naryString localname, XmlDictionaryString ns) 場所 System.Runtime.Serialization.XmlObjectSerializer.IsRootElement(XmlReader Delegator reader, DataContract contract, XmlDictionaryString name, XmlDictionary String ns) 場所 System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalIsS tartObject(XmlReaderDelegator reader) 場所 System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalRea dObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName) 場所 System.Runtime.Serialization.XmlObjectSerializer.InternalReadObject(XmlR eaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContra ctResolver) 場所 System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExcepti ons(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver da taContractResolver) — 内部例外スタック トレースの終わり —

Stream経由で書かなきゃいけないうえ、System.Runtime.Serializationの参照も追加しないといけないので面倒。第一例外になるしこれは正直ないかな

結論:素直にJson.NETを使おう