C# の Json.NET を理解する

C# を使うときに、今までふわっと理解して適当に使っていた Json.NETだけど、一度体系的にしっかり理解してみようと思った。[Introduction](https://www.newtonsoft.com/json/help/html/Introduction.htm)やそのドキュメントを読んで、サンプルを書いて理解してみよう。

現在では、C# の JSON を操作するライブラリとしては、一択感のある Json.NET だが、大きく分けると２つの機能を有する。

* Json を C#のオブジェクトにシリアライズ、デシリアライズする。
* Json を 手動で書いたり、読んだり、クエリーしたりする。LINQ to JSON という名前で呼ばれている。

の大きく２つに分類される。

# シリアライズとデシリアライズ

`SerializeObject` と `DeserializeObject<T>` が最も頻繁に使うメソッドになります。C# のオブジェクトに対して、JSONをシリアライズしたり、デシリアライズします。

```csharp
        public static void Execute()
        {
            var product = new Product()
            {
                Name = "Apple",
                ExpiryDate = DateTimeOffset.Parse("2008/12/28"),
                Price = 3.99M,
                Sizes = new string[] { "Small", "Medium", "Large" }
            };
            string output = JsonConvert.SerializeObject(product);
            Console.WriteLine(output);
            var deserializedProduct = JsonConvert.DeserializeObject<Product>(output);
        }
```

```:実行結果
{"Name":"Apple","ExpiryDate":"2008-12-28T00:00:00-08:00","Price":3.99,"Sizes":["Small","Medium","Large"]}
```
おそらく 90% ぐらいのユースケースでは、この文法を知っていると対応できると思います。しかし、たまに対応できないケースがあります。
例えば私は [durabletask](https://github.com/Azure/durabletask/blob/main/src/DurableTask.Core/TraceContextBase.cs#L41-L43)にコントリビュートしたときに、次の3つのシリアライズの設定をしています。この `TraceContextBase` というオブジェクトはサブクラスがあり、自分の子のオブジェクトのリファレンスを持っています。このオブジェクトをシリアライズして、デシリアライズするためには、どのTraceContextBase のサブクラスがシリアライズされたのかという情報を持つ必要があります。それは、[TypeNameHandling](https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm) という設定で実現されています。具体的には、型名がシリアライズされて保存されます。また、[PreserveReferenceHandling](https://www.newtonsoft.com/json/help/html/PreserveReferencesHandlingObject.htm) によって、参照を持てるようになります。また、[ReferenceLoopHandling](https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm) によって、リファレンスが循環するようなケースも対応されます。それぞれについて解説してみましょう。

```csharp:TraceContextBase.cs
        static TraceContextBase()
        {
            CustomJsonSerializerSettings = new JsonSerializerSettings()
            {
                TypeNameHandling = TypeNameHandling.Objects,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
            };
        }
```

## TypeNameHandling 
Json にシリアライズを行うときに型情報を追加します。例えば、このようなクラスを作成します。インターフェイスとそのサブクラスがあります。

```csharp:Node.cs
    public interface Node
    {
        string Name { get; }
        void Print();
    }

    public class NodeLeaf : Node
    {
        public string Name { get; set; }

        public void Print()
        {
            Console.WriteLine($"I am {this.GetType()}");
        }
    }

    public class NodeComposite : Node
    {
        public string Name { get; set;}

        public void Print()
        {
            Console.WriteLine($"I am {this.GetType()}");
        }
    }
```
このオブジェクトをシリアライズしてみましょう。同じインターフェイスですが実装の型が違います。

```csharp
            var nodes = new List<Node>
            {
                new NodeLeaf { Name = "I'm file."},
                new NodeComposite { Name = "I'm directory."}
            };
```

これを`TypeNameHandling`をつけてシリアライズしてみます。

```csharp
            var typeNameList = JsonConvert.SerializeObject(nodes, Formatting.Indented, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects
            });
            Console.WriteLine("typeNameHandling: " + typeNameList);
```
この箇所はつぎのように表示されます。きれいにシリアライズされています。オブジェクトに `$type`が追加されていて、型情報が追加されているのがわかると思います。このシリアライズの設定も[TypeNameAssemblyFormat](https://www.newtonsoft.com/json/help/html/SerializationSettings.htm)によって実施することが可能です。これを`Full` に設定すると、アセンブリ名やバージョン名などを付加することも可能です。

```:実行結果
typeNameHandling: [
  {
    "$type": "JTokenSPike.NodeLeaf, JTokenSPike",
    "Name": "I'm file."
  },
  {
    "$type": "JTokenSPike.NodeComposite, JTokenSPike",
    "Name": "I'm directory."
  }
]
```
デシリアライズするときも設定を追加します。`JsonSerializerSettings`と、`TypeNameHandling` を設定しないと、この場合は、Exception になります。型情報を読み取らなければ、Node はインターフェイスなのでデシリアライズできないからです。

```csharp
            var deserializedNodeList = JsonConvert.DeserializeObject<IList<Node>>(typeNameList, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Objects
            });

            foreach (var node in deserializedNodeList)
            {
                node.Print();
            }
```

実行結果も思った通りですね。

```:実行結果
I am JTokenSPike.NodeLeaf
I am JTokenSPike.NodeComposite
```

## PreserveReferenceHandling
次に、参照を保持したいときにどうしたらいいかを見てみましょう。

```csharp:DirectoryAndFile
    public class Directory
    {
        public string Name { get; set; }
        public Directory Parent { get; set; }
        public IList<File>? Files { get; set; }

    }

    public class File
    {
        public string Name { get; set; }
        public Directory Parent { get; set; }
    }
```
Directory と File は参照を持っています。これを普通にシリアライズすると、Exception になります。Directory が File
を持っていて、Fileが、Directoryを持っているからです。

```csharp
            try
            {
                JsonConvert.SerializeObject(document, Formatting.Indented);
            } catch (JsonSerializationException ex)
            {
                Console.WriteLine("Expected: " + ex.ToString());
            }
```


```:実行結果
Expected: Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Parent' with type 'JTokenSPike.Directory'. Path 'Files[0]'.
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CheckForCircularReference(JsonWriter writer, Object value, JsonProperty property, JsonContract contract, JsonContainerContract containerContract, JsonProperty containerProperty)
```

次のように設定します。

```csharp:PreserveRerferenceHandling.All
            var preserveReferenceAll = JsonConvert.SerializeObject(document, Formatting.Indented, new JsonSerializerSettings {
                PreserveReferencesHandling = PreserveReferencesHandling.All
            });

            Console.WriteLine("All: " + preserveReferenceAll);
```

`$id` が作成され、参照されているケースはリファレンスとして表現されています。

```:実行結果
All: {
  "$id": "1",
  "Name": "My Documents",
  "Parent": {
    "$id": "2",
    "Name": "Root",
    "Parent": null,
    "Files": null
  },
  "Files": {
    "$id": "3",
    "$values": [
      {
        "$id": "4",
        "Name": "Important Legal Document.docs",
        "Parent": {
          "$ref": "1"
        }
      }
    ]
  }
}
```

シリアライズの方法は、All以外にもあります。Object は、オブジェクトしてシリアライズを行います。

```csharp:Object
            var preserveReferenceObject = JsonConvert.SerializeObject(document, Formatting.Indented, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            });

            Console.WriteLine("Object: " + preserveReferenceObject);
```

先ほどと異なって、Files 部分の下が Array として表現されています。

```:実行結果
Object: {
  "$id": "1",
  "Name": "My Documents",
  "Parent": {
    "$id": "2",
    "Name": "Root",
    "Parent": null,
    "Files": null
  },
  "Files": [
    {
      "$id": "3",
      "Name": "Important Legal Document.docs",
      "Parent": {
        "$ref": "1"
      }
    }
  ]
}
```

他にも Array というのがあります。実行結果は、Allと同じように見えます。ただし、Allと違って、後で解説する`ReferenceLoopHandling` を設定してあげないと、正しく動作しません。

```csharp:Array
            var preserveReferenceArrays = JsonConvert.SerializeObject(document, Formatting.Indented, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Arrays,
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize
            });

            Console.WriteLine("Array: " + preserveReferenceArrays);
```

```:実行結果
Array: {
  "Name": "My Documents",
  "Parent": {
    "Name": "Root",
    "Parent": null,
    "Files": null
  },
  "Files": {
    "$id": "1",
    "$values": [
      {
        "Name": "Important Legal Document.docs",
        "Parent": {
          "Name": "My Documents",
          "Parent": {
            "Name": "Root",
            "Parent": null,
            "Files": null
          },
          "Files": {
            "$ref": "1"
          }
        }
      }
    ]
  }
}
```

Object と、Arrayの違いは何なのでしょう？私の推測でしかないのですが、小さいコードを書いてみます。

```csharp:ObjectAndArray
            var files = new List<File>
            {
                new File {Name = "doc 1"}, new File {Name = "doc 2"}
            };

            var filesObject = JsonConvert.SerializeObject(files, Formatting.Indented, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            });

            Console.WriteLine("Object: " + filesObject);

            var arrayObject = JsonConvert.SerializeObject(files, Formatting.Indented, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Arrays
            });

            Console.WriteLine("Array: " + arrayObject);

```

List だけを作ってシンプルにしました。実行結果の違いは、配列の中としてオブジェクトが表現されているか、オブジェクトの中のValueとして、配列が表現されているかの違いです。ここからはマニュアルから読み取れないので私の推測ですが、JToken という最上位の概念の下に、JObject, JArray という概念が存在します。それと対比していると考えると、Object としてシリアライズするとは、配列をオブジェクトとして解釈しないので、オブジェクトの配列として認識し、Arrayとしてシリアライズするなら、もし Arrayがあったとしたら、JArrayのオブジェクトつまり、配列を表現できるオブジェクトとしてシリアライズするために下記のような仕様になっているのではないでしょうか？

```:実行結果
Object: [
  {
    "$id": "1",
    "Name": "doc 1",
    "Parent": null
  },
  {
    "$id": "2",
    "Name": "doc 2",
    "Parent": null
  }
]
Array: {
  "$id": "1",
  "$values": [
    {
      "Name": "doc 1",
      "Parent": null
    },
    {
      "Name": "doc 2",
      "Parent": null
    }
  ]
}
```

## ReferenceLoopHandling

既に出てきていますが、循環参照をどのように解決するかです。この設定を入れないと、永遠にシリアライズし続けてしまいます。

```csharp:Employee.cs
    public class Employee
    {
        public string Name { get; set; }
        public Employee Manager { get; set; }
    }
```

`ReferenceLoopHandling` の設定付きでシリアライズすると下記のような循環参照もシリアライズ可能です。その際は、`PreserveReferenceHandling`と併用します。

```csharp:ReferenceLoopHandling
            var joe = new Employee { Name = "Joe User" };
            var mike = new Employee { Name = "Mike Manager" };
            joe.Manager = mike;
            mike.Manager = mike;


            var loopReferenceHandling = JsonConvert.SerializeObject(joe, Formatting.Indented, new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize
            });
            Console.WriteLine("Loop: " + loopReferenceHandling);
```

きれいにシリアライズされていますね。

```:実行結果
Loop: {
  "$id": "1",
  "Name": "Joe User",
  "Manager": {
    "$id": "2",
    "Name": "Mike Manager",
    "Manager": {
      "$ref": "2"
    }
  }
}
```

# Linq to Json

いくつかのケースで、自分で Json のオブジェクトを作って、フィルタをかけたいケースがあります。その場合は、`Linq to Json` という機能を使います。Json のオブジェクトは主要なものには次のものがあります。

```:JTokenとサブクラス
JToken             - abstract base class     
   JContainer      - abstract base class of JTokens that can contain other JTokens
       JArray      - represents a JSON array (contains an ordered list of JTokens)
       JObject     - represents a JSON object (contains a collection of JProperties)
       JProperty   - represents a JSON property (a name/JToken pair inside a JObject)
   JValue          - represents a primitive JSON value (string, number, boolean, null)
```

上記のクラス階層になっていますので、Json をパースすると、上記のオブジェクトのツリーが作られます。サンプルを見てみましょう。`Newtonsoft.Json.Linq` namespace を using すると使用できるようになります。`JObject.Parse`メソッドを使うと、オブジェクトがパースできるようになります。

```csharp:LinqToJson
            JObject o = JObject.Parse(@"{
  'CPU': 'Intel',
  'Drives': [
    'DVD read/writer',
    '500 gigabyte hard drive'
  ]
}");

            string cpu = (string)o["CPU"];
            Console.WriteLine($"CPU: {cpu}");

            string firstDrive = (string)o["Drives"][0];
            Console.WriteLine($"First Drive: {firstDrive}");

            IList<string> allDrives = o["Drives"].Select(t => (string)t).ToList();
            foreach (var drive in allDrives)
            {
                Console.WriteLine("Drive: " + drive);
            }
```

Arrayの場合、JArrayでパースすることもできます。

```csharp:Array
            string json = @"[
  'Small',
  'Medium',
  'Large'
]";
            JArray a = JArray.Parse(json);
            Console.WriteLine($"0: {a[0]}, 1: {a[1]}, 2: {a[2]}");
```

```:実行結果
0: Small, 1: Medium, 2: Large
```

StreamReader の `ReadFrom` メソッドを直接読むことも可能です。`computer.json` のファイル名で、先ほどの、CPUと同じJsonを格納しています。インテリセンスによると、Async メソッドも用意されていました。

```csharp
           using(StreamReader reader = System.IO.File.OpenText(@"computer.json"))
            {
                JObject obj = (JObject)JToken.ReadFrom(new JsonTextReader(reader));
                Console.WriteLine($"CPU: {obj["CPU"]}");
            }
```

```:実行結果
CPU: Intel
```
## Linq を使用する

普通に、JObject を使う次のようなコードになり、煩雑ですね。

```csharp
string json = @"{
  'channel': {
    'title': 'James Newton-King',
    'link': 'http://james.newtonking.com',
    'description': 'James Newton-King\'s blog.',
    'item': [
      {
        'title': 'Json.NET 1.3 + New license + Now on CodePlex',
        'description': 'Announcing the release of Json.NET 1.3, the MIT license and the source on CodePlex',
        'link': 'http://james.newtonking.com/projects/json-net.aspx',
        'categories': [
          'Json.NET',
          'CodePlex'
        ]
      },
      {
        'title': 'LINQ to JSON beta',
        'description': 'Announcing LINQ to JSON',
        'link': 'http://james.newtonking.com/projects/json-net.aspx',
        'categories': [
          'Json.NET',
          'LINQ'
        ]
      }
    ]
  }
}";

JObject rss = JObject.Parse(json);

string rssTitle = (string)rss["channel"]["title"];
// James Newton-King

string itemTitle = (string)rss["channel"]["item"][0]["title"];
// Json.NET 1.3 + New license + Now on CodePlex

JArray categories = (JArray)rss["channel"]["item"][0]["categories"];
// ["Json.NET", "CodePlex"]

IList<string> categoriesText = categories.Select(c => (string)c).ToList();
// Json.NET
// CodePlex
```
## Linq を使った例

上記のものを Linq を使ってクエリーしてみます。 シンプルに Select で、タイトルを抽出してみます。素直に実行できていますね。

```csharp
            JObject rss = JObject.Parse(channel);
            var postTitles = rss["channel"]["item"].Select(p => p["title"]);
            foreach(var title in postTitles)
            {
                Console.WriteLine($"Title: {title}");
            };
```

```:実行結果
Title: Json.NET 1.3 + New license + Now on CodePlex
Title: LINQ to JSON beta
```

もう少し複雑なケースで、上記の Json にある、category の部分をクエリーします。二重配列になってしまうので、`SelectMany`を使って、flatten します。その後、[GroupBy](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupby?view=net-6.0)を使って、グループ化した値を使って新しいオブジェクトを作ります。GroupBy の第一引数が、グループ化したい対象を表すための Function で、2つ目の Function は、Functionを実行した結果が、各グループのリストとして、３つ目の Function に渡されます。ここでは、その値を使って、Count() でグループに属するメンバーの数を数えています。最後に降順ソートを実施して終了。

```csharp
            var categories = rss["channel"]["item"].SelectMany(p => p["categories"]).Values<string>()
                .GroupBy(k => k, v => v, (k, vs) => new { Key = k, Value = vs.Count() }).OrderByDescending(p => p.Value);
            foreach(var c in categories) {
                Console.WriteLine($"{c.Key}: {c.Value}");
            }
```

これは楽ちんですね。

```:実行結果
Json.NET: 2
CodePlex: 1
LINQ: 1
```

## 手動で強引にデシリアライズ

本来は、下記の Parse はうまく動作しません。なぜかというと、.NET Ojbect のプロパティと、JSON が一致していないからです。ケースセンシティブだからです。ところが、Parseすると、lower case ではアクセスできるのでそれを利用して強引にデシリアライズを実行しているサンプルです。

```csharp:Shortie
        public class Shortie
        {
            public string Original { get; set; }
            public string Shortened { get; set; }
            public string Short { get; set; }
            public ShortieException Error { get; set; }
        }

        public class ShortieException
        {
            public int Code { get; set; }
            public string ErrorMessage { get; set; }
        }
```

```csharp
string jsonText = @"{
  'short': {
    'original': 'http://www.foo.com/',
    'short': 'krehqk',
    'error': {
      'code': 0,
      'msg': 'No action taken'
    }
  }
}";

JObject json = JObject.Parse(jsonText);

Shortie shortie = new Shortie
{
    Original = (string)json["short"]["original"],
    Short = (string)json["short"]["short"],
    Error = new ShortieException
    {
        Code = (int)json["short"]["error"]["code"],
        ErrorMessage = (string)json["short"]["error"]["msg"]
    }
};

Console.WriteLine(shortie.Original);
// http://www.foo.com/

Console.WriteLine(shortie.Error.ErrorMessage);
// No action taken
```

## SelectToken

SelectToken を書くと、クエリをダイナミックに定義することも可能です。ちなみに、SeelctTokenは、[JsonPath](http://goessner.net/articles/JsonPath/)もサポートしていますので、かなり複雑なことが出来そうですが、それはそれで頭が混乱しそうなので、SelectTokenと、Linq の組み合わせで十分な気がしますので、あまり調べないことにします。LinqToJsonの最初のサンプルを使ってクエリしてみましょう。

```csharp:SelectToken
            var link = (string)rss.SelectToken("channel.item[1].link");
            Console.WriteLine($"SelectToken: Link: {link}");
```

```:実行結果
SelectToken: Link: http://james.newtonking.com/projects/json-net.aspx
```

# まとめ

これでざっくり知りたかったことはわかったので、このブログは一旦終了です。Json.NET のページに行くと [Performance Tips](https://www.newtonsoft.com/json/help/html/Performance.htm) とか [SerializationAttributes](https://www.newtonsoft.com/json/help/html/SerializationAttributes.htm)とか面白そうなところは残っていますが、今回のブログは一旦ここまで。また次のお楽しみにしておきます。