たくのろじぃのメモ部屋

プログラミング(C#)の基礎やそれを応用した技術情報をメモしておくブログです。

【C#】JSONファイルを読み込む

JSONファイル読み込みを行うことになったのでやり方をメモ。

1. JSONファイルの読み込み

まずはjsonファイルのパスを指定して内容を読み込みます。jsonldファイルもいけます。

string FilePath = @"hoge/huga.json"; //ファイルパス
var json = File.ReadAllText(FilePath); // ファイル内容をjson変数に格納

jsonを読み込むにはメモリストリームが必要です。メモリストリームはデータの読み込みをバイト単位で行えます。 エンコードはUTF8で行います。

var ms = new MemoryStream(Encoding.UTF8.GetBytes((json)));

2. jsonデータの格納

また、jsonにはKeyとValueといった具合にデータが記述されていますので、これを取り出してValueを変数に格納したいところです。 jsonのKeyを取得するにはデータコントラクト属性を用います。

データコントラクト属性はもともとXmlファイルのデータをシリアル化するものでしたが、jsonにも対応しています。シリアライズ(書き込み)とデシリアライズ(読み込み)ができます。

例えば、jsonファイルの "id" 属性を取得したい場合はデータコントラクト属性のクラスを作成し、データメンバ属性でメンバ変数を宣言します。データメンバの Name に取得したいjsonのKeyを入れると、その値が取得できます。ただし、Key名が正しくないと取得できないので注意が必要です。Valueも型を合わせます。

[DataContract] //データコントラクト属性
public partial class JsonData
{
    [DataMember(Name = "id")] //データメンバ属性
    public int Value { get; set; }
}

このようにしてjsonから取得したいデータをデータコントラクトになげて、そのデータをメンバ変数で管理します。

3. JSONファイルのデータ格納

jsonファイルの読み込みは先頭ビットから読み込みたいので、ストリームの位置を先頭にします。読み込み次第、そのデータを変数に格納したいので、as演算子で変数を宣言したクラスに変換します。

ms.Seek(0, SeekOrigin.Begin); // ストリームの先頭
var data = serializer.ReadObject(ms) as JsonData; //データをJsonDataクラスに変換

あとはデータが正しく入力されているかを確認するだけです。

Console.WriteLine($"ID : {data.Value}");

4. 試した結果

私が読み込みたいのはちょっと複雑なjsonでしたので、いくつかクラスを作って試してみました。 読み込むjsonファイルはこちらです。人狼ゲームのデータです。

github.com

ちなみに、このjsonファイル、配列の中に構造体が入っているような複雑なファイルです。なので、クラスの中にクラスをいれることで配列の中に構造体を格納するようなデータ構造にします。

例えば、morning.jsonldファイルの67行目からcharacter配列の中には"@id"や"@context"などが入っていますが、72行目"name"が構造体になっています。なので、クラス1つだけではデータを正しく受け取れません。このままプログラムを実行すると例外 (item要素が~的な例外) が発生します。

そこで、characterクラスの中にnameクラスを作成すれば解決です。jsonのname構造体にはjaとenのKeyが含まれているので、これらを宣言します。

[DataContract]
public partial class Character
{
    [DataMember(Name = "name")]
    public Name Name { get; set; } //Nameクラスを参照
}

[DataContract]
public partial class Name //Nameクラス
{
    [DataMember(Name = "en")]
    public string En { get; set; }

    [DataMember(Name = "ja")]
    public string Ja { get; set; }
}

これで配列の中にある構造体データを格納できます。これに合わせれば、配列の中に配列や構造体の中に構造体といった、複雑なデータ構造を扱えます。名付けて ”クラスの中にクラス大作戦” とかですかね...。

これを用いて、実際にやってみました。

string FilePath = @"C:\werewolfworld-gh-pages\village\example\0.3\server2client\morning.jsonld";
var serializer = new DataContractJsonSerializer(typeof(JsonData));
var json = File.ReadAllText(FilePath);
var ms = new MemoryStream(Encoding.UTF8.GetBytes((json)));
ms.Seek(0, SeekOrigin.Begin);
var data = serializer.ReadObject(ms) as JsonData;

for (int i = 0; i < data.Context.GetLength(0); i++)
Console.WriteLine($"context{i} : {data.Context[i].ToString()}");

Console.WriteLine($"Token : {data.Token}");
Console.WriteLine($"Phase : {data.Phase}");
Console.WriteLine($"Date : {data.Date}");
Console.WriteLine($"PhaseTimeLimit : {data.PhaseTimeLimit}");
Console.WriteLine($"ServerTimestamp : {data.ServerTimestamp}");
Console.WriteLine($"ClientTimestamp : {data.ClientTimestamp}");

for(int i = 0; i<data.Character.GetLength(0); i++) //キャラクター情報の表示
{
    Console.Write($"参加者ID {data.Character[i].CharacterId} : "); //プレイヤーID
    Console.Write($"{data.Character[i].Name.Ja} ({data.Character[i].Name.En})"); // プレイヤー名
    Status = data.Character[i].Status;
    if(Status == "alive")
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($" {data.Character[i].Status}");
        Console.ForegroundColor = ConsoleColor.White;
    }
    else if(Status == "dead")
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.ForegroundColor = ConsoleColor.White;
    }
                
}

JsonDataクラスはこんな感じです

[DataContract]
public class JsonData
{
    [DataMember(Name = "@context")]
    public string[] Context { get; set; }

    [DataMember(Name = "@id")]
    public string Id { get; set; }

    [DataMember(Name = "token")]
    public string Token { get; set; }

    [DataMember(Name = "phase")]
    public string Phase { get; set; }

    [DataMember(Name = "date")]
    public int Date { get; set; }

    [DataMember(Name = "phaseTimeLimit")]
    public int PhaseTimeLimit { get; set; }

    [DataMember(Name = "serverTimestamp")]
    public string ServerTimestamp { get; set; }

    [DataMember(Name = "clientTimestamp")]
    public string ClientTimestamp { get; set; }

    [DataMember(Name = "character")]
    public Character[] Character { get; set; }
}

[DataContract]
public partial class Character
{
    [DataMember(Name = "@context")]
    public Uri Context { get; set; }

    [DataMember(Name = "@id")]
    public Uri Id { get; set; }

    [DataMember(Name = "isMine")]
    public bool IsMine { get; set; }

    [DataMember(Name = "name")]
    public Name Name { get; set; }

    [DataMember(Name = "image")]
    public Uri Image { get; set; }

    [DataMember(Name = "id")]
    public int CharacterId { get; set; }

    [DataMember(Name = "status")]
    public string Status { get; set; }

    [DataMember(Name = "update")]
    public Update Update { get; set; }

    [DataMember(Name = "isAChoice")]
    public bool IsAChoice { get; set; }
}

[DataContract]
public partial class Name
{
    [DataMember(Name = "en")]
    public string En { get; set; }

    [DataMember(Name = "ja")]
    public string Ja { get; set; }
}

[DataContract]
public partial class Update
{
    [DataMember(Name = "@id")]
    public Uri Id { get; set; }

    [DataMember(Name = "phase")]
    public string Phase { get; set; }

    [DataMember(Name = "date")]
    public long Date { get; set; }
}

これで動かしてみます。

f:id:takunology:20191113000648p:plain

構造体の中身が表示されています。

今回作成したコード

参考にどうぞ。

github.com

参考サイト

参考にしたサイトです。ありがとうございます。

シリアライズ

blog.okazuki.jp

データコントラクト

docs.microsoft.com