Edited at

【Json.NET】ネストされたJsonObjectのメンバーをネストされていないJsonObjectと同じクラスのプロパティへDeserializeする

More than 1 year has passed since last update.


概要

Jsonでネストされている部分のメンバーをネストされていないメンバーと同じレベルで扱いたい。

通常はネストされている部分ごとにObjectを定義すべきだが、


  • 最初はスクレイピングでデータを取ってきていたけど後からAPIを立ててデータをJsonでやり取りするようにした

  • すでにアプリで使用しているローカルのデータベース構造をあんまりいじるとデータの整合性を保つのが困難

というクラス設計の甘さからきた苦し紛れの解決策です。


サンプルのJsonファイル


employee.json

{

"id":1
"name": "Suzukaze Aoba",
"company": {
"id":1
"name": "EagleJump",
"department": {
"id":1
"name": "Graphic"
}
},
"projects": [
{
"id":1
"name": "FAIRIES STORY"
},
{
"id":2
"name": "PECO"
}
]
}


DeserializeさせたいObject


Employee.cs


public class Employee
{

public int ID { get; set; }

public string Name { get; set; }

public string CompanyName { get; set; } //ここに"company"配下の"name"を割り当てたい

public string DepartmentName { get; set; } // ここに"company"配下の更に"department"配下の"name"を割り当てたい

public string ProjectName { get; set; } //ここに"projects"の最初の要素の"name"を割り当てたい

}



解決策

JsonConverterを継承したクラスを作り、Deserialize時に変換操作を行わせる


ObjectMemberPicker.cs

using System;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class ObjectMemberPicker : JsonConverter
{
private string _memberPath = "";

public ObjectMemberPicker(string memberPath)
{
this._memberPath = memberPath;
}

public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}

public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return jo.SelectToken(this._memberPath).ToObject(objectType);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}



ArrayMemberPicker.cs


using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

class ArrayMemberPicker:JsonConverter
{
private string _memberPath = "";

public ArrayMemberPicker(string memberPath)
{
this._memberPath = memberPath;
}

public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}

public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JArray jo = JArray.Load(reader);
return jo.SelectToken(this._memberPath).ToObject(objectType);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}



Converter適用後のDeserializeさせたいObject


Employee.cs

using Newtonsoft.Json;

public class Employee
{

public int ID { get; set; }

public string Name { get; set; }

// JsonPropertyでDeserializeしたいメンバーを持つJsonObject名を指定
// JsonConverterの宣言でObjectMemberPickerの型と指定したいPathをパラメーターで渡す
// (このパラメーターがObjectMemberPickerのコンストラクタへ引数として渡される)
[JsonProperty("company")]
[JsonConverter(typeof(ObjectMemberPicker), "name")]
public string CompanyName { get; set; }

// 一つのJsonPropertyは一つのPropertyにしか割り当てられないようなので、
// 今のところDepartmentNameにDeserializeするならComopanyNameは諦めるしかない
// Deserialize自体は下記のPath指定でできた。

//[JsonProperty("company")]
//[JsonConverter(typeof(ObjectMemberPicker), "department.name")]
//public string DepartmentName { get; set; }

[JsonProperty("projects")]
[JsonConverter(typeof(ArrayMemberPicker), "[0].name")]
public string ProjectName { get; set; }

}



まとめ

とりあえず当初の目的はクリアできた。

あとは


  • 同レベルで扱いたいネストされたメンバーが複数になった時

をどうするか・・・・

参考にしたstackoverflowの記事では、クラスにConverterを指定する形式で

もうちょっと複雑なロジックを組んでいて、JsonProperty自体にPathを設定していたけど、

プロパティごとにConverterを切り替えたかったりするときにうまく動かなかった。

何か良い方法はないのだろうか・・・

テストに使ったコードを置いておきますのでアドバイスあればお願い致します。


DeserialiseTest.cs

using System;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

[TestClass]
public class DeserializeTest
{
[TestMethod]
public void EmployeeDeserializeTest()
{
var employeeJson = "{\"id\":1,\"name\":\"SuzukazeAoba\",\"company\":{ \"id\":1,\"name\":\"EagleJump\",\"department\":{ \"id\":1,\"name\":\"Graphic\"} },\"projects\":[{\"id\":1,\"name\":\"FAIRIES STORY\"},{\"id\":2,\"name\":\"PECO\"}]}";

var employee = JsonConvert.DeserializeObject<Employee>(employeeJson);

Assert.AreEqual(1, employee.ID);
Assert.AreEqual("SuzukazeAoba", employee.Name);
Assert.AreEqual("EagleJump", employee.CompanyName);
//Assert.AreEqual("Graphic", employee.DepartmentName);
Assert.AreEqual("FAIRIES STORY", employee.ProjectName);
}
}