🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

3回答

2297閲覧

正常系/異常系で異なる構造のjsonを返すAPIのレスポンスを一つのクラスで扱いたい

uchu_beer

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2021/01/14 10:39

編集2021/01/14 10:49

前提

あるAPIと通信し、結果を返すモジュールを作成しています。
APIはjson形式で結果を返し、モジュール内でJson.NETを使ってエンティティクラスにデシリアライズを行っています。
モジュールでは複数のAPI呼び出しがあり、レスポンスのエンティティクラスは一つの抽象クラスを継承し、APIごとに作成しています。

発生している問題・エラーメッセージ

APIはリクエストを正常に処理すると、下記の様なレスポンスを返すのですが、

JSON

1{ 2 "token": "abcdefghijklmnopqrstuvwxyz" 3}

リクエストを処理できなかった場合、下記の様なレスポンスを返します。

JSON

1[ 2 { 3 "error_code": "E01", 4 "error_information": "hogehoge" 5 }, 6 { 7 "error_code": "E02", 8 "error_information": "mogemoge" 9 } 10]

どのAPIでもエラー時に返ってくるデータのフォーマットは同一なので、すべてのレスポンスクラスの親は下記のような実装になっています。

c#

1public abstract class Response 2{ 3 public Error[] Errors { get; set; } 4 5 public class Error 6 { 7 [JsonProperty("error_code")] 8 public string ErrorCode { get; set; } 9 10 [JsonProperty("error_information")] 11 public string ErrorInformation { get; set; } 12 } 13}

各APIのレスポンスクラスはこのように実装しています。

C#

1public class HogeApiResponse : Response 2{ 3 [JsonProperty("token")] 4 public string Token { get; set; } 5}

しかし、上記のコードでは、エラー時のJSONをデシリアライズできず、例外が発生してしまいます。

Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'toybox.Response' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.

試したこと

エラーメッセージに従って、ResponseクラスにIEnumerable<Error>を実装したりJsonArrayをつけたりしてみましたが正常にデシリアライズはできませんでした。

実現したいこと

APIがエラーを返したとき、他のプロパティはNull等でも構わないのでError[]にエラー情報が格納された状態でデシリアライズを行いたいです。
実際のコードではジェネリック等使って型を識別しているので、なんとか一つのクラスへのデシリアライズで完結させたいと思っています。

検証用のコード

実際のコードでテストするのは時間がかかるので、コンソールアプリを一つ作ってテストしています。
同じ様な例外が飛びます。

C#

1using System.Collections.Generic; 2using Newtonsoft.Json; 3 4namespace toybox 5{ 6 static class Program 7 { 8 static void Main(string[] args) 9 { 10 var r = JsonConvert.DeserializeObject<ResponseImpl>(@"[{""error_code"":""E01"",""error_information"":""hogemoge""}]"); 11 } 12 } 13 14 public abstract class Response 15 { 16 public List<Error> Errors { get; set; } 17 18 public class Error 19 { 20 [JsonProperty("error_code")] 21 public string ErrorCode { get; set; } 22 23 [JsonProperty("error_information")] 24 public string ErrorInformation { get; set; } 25 } 26 } 27 28 public class ResponseImpl : Response 29 { 30 31 } 32} 33

補足情報(FW/ツールのバージョンなど)

.NET Framework 4.5.2

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/01/14 10:52

エラーメッセージにある 'toybox.Response' とは何ですか?
uchu_beer

2021/01/14 10:53

デシリアライズしようとしているクラスの名前空間とクラス名です。 検証に使っているコードを追加しているのでご確認ください
退会済みユーザー

退会済みユーザー

2021/01/14 12:03 編集

> @"[{""error_code"":""E01"",""error_information"":""hogemoge""}]" これが変なのですが、そこはとりあえず置いといても、そもそも [ { ... },{ ... },{ ... } ] という形の JSON 文字列はデシリアライズきなくて、例えば { "name": [ { ... },{ ... },{ ... } ] } というような形の JSON 文字列する必要があると思います。
guest

回答3

0

解決されたようですが気になったので調査しました。
JsonConverterを書けば何とかなりました。動いてはいますが正しいかはわかりません^^;

cs

1using Newtonsoft.Json; 2using Newtonsoft.Json.Linq; 3using System; 4using System.Linq; 5 6namespace Questions315978 7{ 8 class Program 9 { 10 static void Main() 11 { 12 var errorJson = @" 13[ 14 { 15 ""error_code"": ""E01"", 16 ""error_information"": ""hogehoge"" 17 }, 18 { 19 ""error_code"": ""E02"", 20 ""error_information"": ""mogemoge"" 21 } 22]".Trim(); 23 //var r0 = JsonConvert.DeserializeObject<Response.Error[]>(errorJson); 24 var r1 = JsonConvert.DeserializeObject<HogeApiResponse>(errorJson); 25 26 27 var successJson = @" 28{ 29 ""token"": ""abcdefghijklmnopqrstuvwxyz"" 30}".Trim(); 31 var r2 = JsonConvert.DeserializeObject<HogeApiResponse>(successJson); 32 } 33 } 34 35 public class ResponseJsonConverter : JsonConverter 36 { 37 public override bool CanConvert(Type objectType) 38 => typeof(Response).IsAssignableFrom(objectType); 39 40 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 41 { 42 if (reader.TokenType == JsonToken.StartArray) 43 { 44 var jArray = JArray.Load(reader); 45 var target = (Response)Activator.CreateInstance(objectType); 46 target.Errors = jArray.Select(x => JsonConvert.DeserializeObject<Response.Error>(x.ToString())).ToArray(); 47 return target; 48 } 49 else 50 { 51 var jObject = JObject.Load(reader); 52 var target = Activator.CreateInstance(objectType); 53 serializer.Populate(jObject.CreateReader(), target); 54 return target; 55 } 56 } 57 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 58 => throw new NotImplementedException(); 59 } 60 61 [JsonConverter(typeof(ResponseJsonConverter))] 62 public abstract class Response 63 { 64 public Error[] Errors { get; set; } 65 66 public class Error 67 { 68 [JsonProperty("error_code")] 69 public string ErrorCode { get; set; } 70 71 [JsonProperty("error_information")] 72 public string ErrorInformation { get; set; } 73 } 74 } 75 76 public class HogeApiResponse : Response 77 { 78 [JsonProperty("token")] 79 public string Token { get; set; } 80 } 81}

ついでにSystem.Text.Json版もやってみたのですが、さらに自信ありません^^;

cs

1using System; 2using System.Text.Json; 3using System.Text.Json.Serialization; 4 5namespace Questions315978 6{ 7 class Program 8 { 9 static void Main() 10 { 11 var errorJson = @" 12[ 13 { 14 ""error_code"": ""E01"", 15 ""error_information"": ""hogehoge"" 16 }, 17 { 18 ""error_code"": ""E02"", 19 ""error_information"": ""mogemoge"" 20 } 21]".Trim(); 22 var options = new JsonSerializerOptions(); 23 options.Converters.Add(new ResponseJsonConverter()); 24 //var r0 = JsonSerializer.Deserialize<Response.Error[]>(errorJson); 25 var r1 = JsonSerializer.Deserialize<HogeApiResponse>(errorJson, options); 26 27 28 var successJson = @" 29{ 30 ""token"": ""abcdefghijklmnopqrstuvwxyz"" 31}".Trim(); 32 var r2 = JsonSerializer.Deserialize<HogeApiResponse>(successJson, options); 33 } 34 } 35 36 public class ResponseJsonConverter : JsonConverter<Response> 37 { 38 public override bool CanConvert(Type typeToConvert) 39 => typeof(Response).IsAssignableFrom(typeToConvert); 40 41 public override Response Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 42 { 43 if (reader.TokenType == JsonTokenType.StartArray) 44 { 45 var target = (Response)Activator.CreateInstance(typeToConvert); 46 target.Errors = JsonSerializer.Deserialize<Response.Error[]>(ref reader); 47 return target; 48 } 49 else 50 { 51 // options渡すと当然StackOverflowException 52 return (Response)JsonSerializer.Deserialize(ref reader, typeToConvert); 53 } 54 } 55 public override void Write(Utf8JsonWriter writer, Response value, JsonSerializerOptions options) 56 => throw new NotImplementedException(); 57 } 58 59 // こうも書けるのだが派生に引き継がれないっぽいのと、 60 // 優先度が高すぎてStackOverflowExceptionを抑えられない^^; 61 //[JsonConverter(typeof(ResponseJsonConverter))] // ダメ! 62 public abstract class Response 63 { 64 public Error[] Errors { get; set; } 65 66 public class Error 67 { 68 [JsonPropertyName("error_code")] 69 public string ErrorCode { get; set; } 70 71 [JsonPropertyName("error_information")] 72 public string ErrorInformation { get; set; } 73 } 74 } 75 76 //[JsonConverter(typeof(ResponseJsonConverter))] // ダメ! 77 public class HogeApiResponse : Response 78 { 79 [JsonPropertyName("token")] 80 public string Token { get; set; } 81 } 82}

投稿2021/01/15 16:17

編集2023/07/26 13:08
TN8001

総合スコア9862

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

かなりイケてない実装ではありますが、下記のようにして一旦解決しました(納期も余裕ないので…)

C#

1var json = @"[{""error_code"":""E01"",""error_information"":""hogemoge""}]"; 2var r = JsonConvert.DeserializeObject<ResponseImpl>( 3 json.StartsWith("[") 4 ? @"{""errors"":" + json + "}" 5 : json);

投稿2021/01/14 16:32

uchu_beer

総合スコア1

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/01/14 20:47

あたかも自己解決したように書いてありますが、それは私の回答そのものではないですか?
guest

0

ベストアンサー

質問のコメントに書きましたが、そもそも [ { ... },{ ... },{ ... } ] という形の JSON 文字列はデシリアライズきなくて、{ "name": [ { ... },{ ... },{ ... } ] } というような形の JSON 文字列する必要があると思います。

例えばリクエストを処理できなかった場合に返す JSON 文字列を以下のようにできればデシリアライズできるはずです。

{ "Errors" : [ { "error_code": "E01", "error_information": "hogehoge" }, { "error_code": "E02", "error_information": "mogemoge" } ] }

検証してみましたが、クラス定義を以下のようにして(質問者さんのコードと等価のはず)、

public class ResponseImpl { public string Token { get; set; } public List<Error> Errors { get; set; } } public class Error { public string Error_code { get; set; } public string Error_information { get; set; } }

以下の画像の通りデシリアライズできます。

イメージ説明

投稿2021/01/14 12:02

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

uchu_beer

2021/01/14 16:31

確かにあまり見ない形のJsonではあるんですが、下記コードだとデシリアライズ可能でしたので、Jsonとしておかしい形式というわけではないんだと思います… ``` var r = JsonConvert.DeserializeObject<List<Response.Error>>(@"[{""error_code"":""E01"",""error_information"":""hogemoge""}]"); ``` ですが確かに {"errors":[{~}]}の形式にするのが早そうですね
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問