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

#概要

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

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

- 最初はスクレイピングでデータを取ってきていたけど後からAPIを立ててデータをJsonでやり取りするようにした
- すでにアプリで使用しているローカルのデータベース構造をあんまりいじるとデータの整合性を保つのが困難

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

#サンプルの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

```csharp: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時に変換操作を行わせる


```csharp: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();
        }
    }
```

```csharp: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

```csharp: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の記事](https://stackoverflow.com/questions/33088462/can-i-specify-a-path-in-an-attribute-to-map-a-property-in-my-class-to-a-child-pr)では、クラスにConverterを指定する形式で
もうちょっと複雑なロジックを組んでいて、JsonProperty自体にPathを設定していたけど、
プロパティごとにConverterを切り替えたかったりするときにうまく動かなかった。

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

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

```csharp: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);
        }
    }
```