Posted at

JSON.NETのCustom Converterを書く

More than 1 year has passed since last update.

JSON.NETはConverterを書かなくてもプロパティを読んでくれてjsonに変換してくれるのですが、変換された形式が望んでいるものとは違うこともあります。

{

"title": "HelloWorld!",
"tags": [ "Diary", "Programming", "C#" ],
"content": "...",
"comments": [
{
"author": "Alice",
"content": "hogehoge!"
},
{
"author": "Bob",
"content": "piyopiyo!"
}
]
}

このjsonを以下のようなクラスに変換する。

    public class Post

{
public string Title { get; set; }
public string Content { get; set; }
public IList<Tag> Tags { get; set; } = new List<Tag>();
public IList<Comment> Comments { get; set; } = new List<Comment>();
}

public class Tag
{
public string Name { get; set; }
}

public class Comment
{
public string Author { get; set; }
public string Content { get; set; }
}

JsonConverterを継承したクラスを作ることで特定のクラスのCustom Converterを作ることができます。

継承したクラスでオーバーライドするメソッドは以下。

class JsonConverter

{
public bool CanConvert(Type objectType);
public object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer);
public void WriteJson(JsonWriter writer, object value, JsonSerializer serializer);
}

そしてこんなふうに実際のConverterは以下のようになる。

    /// <summary>

/// Postをjsonに変換
/// </summary>
public class PostConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Post);

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var post = new Post();

while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) break;
if (reader.TokenType == JsonToken.PropertyName)
{
var prop = reader.Value?.ToString();
reader.Read();

switch (prop)
{
case "tags":
post.Tags = new JsonSerializer().Deserialize<IList<Tag>>(reader);
break;
case "comments":
post.Comments = new JsonSerializer().Deserialize<IList<Comment>>(reader);
break;
case "title":
post.Title = reader.Value.ToString();
break;
case "content":
post.Content = reader.Value.ToString();
break;
}
}
}

return post;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var post = value as Post;
if (post == null) return;

writer.WriteStartObject();

writer.WritePropertyName("title");
writer.WriteValue(post.Title);
writer.WritePropertyName("content");
writer.WriteValue(post.Content);
writer.WritePropertyName("tags");
JToken.FromObject(post.Tags).WriteTo(writer);
writer.WritePropertyName("comments");
JToken.FromObject(post.Comments).WriteTo(writer);

writer.WriteEndObject();
}
}

/// <summary>
/// Tagをjsonに変換
/// </summary>
public class TagConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Tag);

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var tag = new Tag();

tag.Name = reader.Value.ToString();

return tag;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var tag = value as Tag;
if (tag == null) return;

writer.WriteValue(tag.Name);
}
}

/// <summary>
/// Commentをjsonに変換
/// </summary>
public class CommentConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Comment);

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var props = new string[] { "author", "content" };
var comment = new Comment();

while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) break;
if (reader.TokenType == JsonToken.PropertyName)
{
var prop = reader.Value.ToString();
reader.Read();

switch (prop)
{
case "author":
comment.Author = reader.Value.ToString();
break;
case "content":
comment.Content = reader.Value.ToString();
break;
}
}
}

return comment;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var comment = value as Comment;
if (comment == null) return;

writer.WriteStartObject();

writer.WritePropertyName("author");
writer.WriteValue(comment.Author);
writer.WritePropertyName("content");
writer.WriteValue(comment.Content);

writer.WriteEndObject();
}
}

ちょっと長いけど、それぞれでやってることは

CanConvertで変換できるクラスを指定する。

ReadJsonではreaderから一つずつトークンを読み込み、インスタンスに値を次々設定していく。

WriteJsonは引数で変換するインスタンスが渡されるので、writerに書き込む。

各クラスにJsonConverter属性を指定します。

    [JsonConverter(typeof(PostConverter))]

public class Post
{
...
}
[JsonConverter(typeof(TagConverter))]
public class Tag
{
...
}

[JsonConverter(typeof(CommentConverter))]
public class Comment
{
...
}

これだけ書いたらDeserializeObjectで変換できる。

var post = JsonConvert.DeserializeObject<Post>(jsonText);