LoginSignup
5
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-25

概要

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);
        }
    }
5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6