C#でJSONを扱う記事は探せばたくさん出てくるのですが、そのほとんどが
・一段階のフラットJSONしか扱ってない
・先に展開先クラスを用意する方法
のどちらかしか扱っていません。
それらの方法では私のやりたいことができなかったので、そこらへんについて調べた記録です。
Microsoft公式のSystem.Text.Jsonを使ってJSONを扱うためには、先に展開したいJSONと相似形のクラスを用意しておく必要があります。
以下では公式のJSONサンプルを例に、操作を行ってみます。
// 展開する先のクラス構造publicclassWeatherForecastWithPOCOs{publicDateTimeOffsetDate{get;set;}publicintTemperatureCelsius{get;set;}publicstringSummary{get;set;}publicstringSummaryField;publicIList<DateTimeOffset>DatesAvailable{get;set;}publicDictionary<string,HighLowTemps>TemperatureRanges{get;set;}publicstring[]SummaryWords{get;set;}}publicclassHighLowTemps{publicintHigh{get;set;}publicintLow{get;set;}}// JSON文字列の例stringjsonString="{\"Date\":\"2019-08-01T00:00:00-07:00\",\"TemperatureCelsius\":25,\"Summary\":\"Hot\",\"DatesAvailable\":[\"2019-08-01T00:00:00-07:00\",\"2019-08-02T00:00:00-07:00\"],\"TemperatureRanges\":{\"Cold\":{\"High\":20,\"Low\":-10},\"Hot\":{\"High\":60,\"Low\":20}},\"SummaryWords\":[\"Cool\",\"Windy\",\"Humid\"]}";// デシリアライズvarweatherForecast=JsonConvert.DeserializeObject<WeatherForecastWithPOCOs>(jsonString);
めんどくさすぎる…
何が困るって、APIとかの外部からやってくる不定形のJSONをパースしたいのですよ。
APIなんて形がすぐ変化するので、先に形式を用意しておかないといけないSystem.Text.Jsonは全くもってこの世界には向いていません。
もっとこう$obj = json_decode($jsonString)
みたいなのはないんですかね。
実のところSystem.Text.Jsonはかなり機能が少ないです。
このような用途にはNewtonsoft.Jsonのほうが向いています。
// Newtonsoft.Jsonを使うvarweatherForecast=JsonConvert.DeserializeObject(jsonString);
こうするとクラスを用意しておく必要がなくなるのですが、返ってくるのはobjectなのでいまいち役に立ちません。
全部Dictionaryあたりにしてほしいのですが。
…普通にNewtonsoft.Json公式にあったわ。
Dictionary<string,string>weatherForecast=JsonConvert.DeserializeObject<Dictionary<string,string>>(jsonString);
やったか!
と思いきやDatesAvailable
あたりはstringじゃないので当然ながら死にます。
パース方法はDataSetとかImmutableListとか色々ありますが、どれも全ての値が同じ型で、段階がフラットであることを前提としたものです。
中身が入れ子になってたりなってなかったりするJSONを、全部連想配列か何か展開してほしいのですよ。Dictionary<string, typeof(Value)>
みたいに書けないものですかね。
それからなんやかんやあって、最終的にJObjectを使ってどうにかすることができました。
// デシリアライズJObjectweatherForecast=JObject.Parse(jsonString);// 直下の値を取得Console.WriteLine("Date: "+weatherForecast["Date"].ToString());// 入れ子の値を取得Console.WriteLine("TemperatureRanges_Cold_High: "+weatherForecast["TemperatureRanges"]["Cold"]["High"].ToString());// []varDatesAvailable=weatherForecast["DatesAvailable"].Children();foreach(varDatesAvailablinDatesAvailable){Console.WriteLine("DatesAvailable: "+DatesAvailabl.ToString());}// {}JEnumerable<JToken>TemperatureRanges=weatherForecast["TemperatureRanges"].Children();foreach(JTokenTemperatureRangeinTemperatureRanges){// キーConsole.WriteLine("TemperatureRange Key: "+((JProperty)TemperatureRange).Name);// 値Console.WriteLine("TemperatureRange Value: "+TemperatureRange.First["High"]);// 一部だけ定型クラスに取り出したいHighLowTempshighLowTemps=TemperatureRange.First.ToObject<HighLowTemps>();}// トップレベルに追加weatherForecast["hoge"]="fuga";// 削除weatherForecast.Property("Summary").Remove();// []に追加((JArray)weatherForecast["DatesAvailable"]).Add("2020-02-02T02:02:02-02:00");// {}に追加((JObject)weatherForecast["TemperatureRanges"]).Last.AddAfterSelf(newJProperty("Lukewarm",JObject.Parse(@"{""High"": 10,""Low"": 10}")));// これでもいいweatherForecast["TemperatureRanges"]["Absolute"]=JObject.Parse(@"{""High"": -273,""Low"": -273}");// JSON文字列に戻すvarjsonStringAfter=JsonConvert.SerializeObject(weatherForecast);
とりあえずこれで、C#でJSONをそこそこ自在に操ることができるようになったと思います。
PHPならどれも一瞬でできるのに……とか思いつつここまで調べるのに半日かかった。
なおサンプルはそのままだと.ToList()
のあたりでエラーになるので、どこかで仕様が変わったようです。
このくらいの記事ってPHPなら腐るほど転がってると思うのですが、C#だとMicrosoftのドキュメントか、ほとんど公式のコピペのような内容しか見つからないんですよね。
それらを応用してどうこうするって内容がどうにもなかなか存在しない。
独学でレベルアップするには非常に厳しい言語だと思います。
いや、この程度書くほどでもない常識だからどこにも書いてないんだよ、とか言われたら死にます。