質問をすることでしか得られない、回答やアドバイスがある。

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

ただいまの
回答率

88.23%

LinkedHashMapを指定クラスにキャスト出来ない

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 11K+

rontec

score 161

HTTP Client Library for Javaで通信したInstagramのAPIのレスポンスの受け皿クラスを作成し、キャストしようと企んでいます。

以下キャスト用クラスです。

@Getter
public class クラスA extends GenericJson{
    @Key("pagination")
    private HashMap<String, String> pagination;
    @Key("meta")
    private HashMap<String, String> meta;
    @Key("data")
    private List<DataBreakdown> data;

    ~

    // inner class
    @Getter
    class DataBreakdown{
        @Key("attribution")
        private Object attribution;
        @Key("tags")
        private List<String> tsgs;
        @Key("type")
        private String type;
        @Key("location")
        private Object location;
        @Key("comments")
        private HashMap<String, Object> comments;
        @Key("filter")
        private Object filter;
        @Key("created_time")
        private Object created_time;
        @Key("link")
        private String link;
        @Key("likes")
        private HashMap<String, Object> likes;
        @Key("images")
        private HashMap<String, Object> images;
        @Key("users_in_photo")
        private Object users_in_photo;
        @Key("caption")
        private HashMap<String, Object> caption;
        @Key("user_has_liked")
        private Object user_has_liked;
        @Key("id")
        private String id;
        @Key("user")
        private HashMap<String, String> user;
    }

実行したところ、下記拡張forでエラーになります。

    public List<HashMap<String, Object>> getInstagramImageData() {
        List<HashMap<String, Object>> instagramImageList = new ArrayList<>();

        for (DataBreakdown oneData : data) {
            HashMap<String, Object> instagramImageData = new HashMap<>();
            instagramImageData.put("images", oneData.getImages());
            instagramImageList.add(instagramImageData);
        }
        return instagramImageList;
    }

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to クラスA$DataBreakdown
※クラス名は「クラスA」に変換しています

 @Key("data")をHashMap<String, Object>にマッピング、objectで扱う分には特に問題ありません。
ただそうすると @Key("data")の中身をjavaで扱いづらいので、なんとか作成したクラスにマッピングしたいのですが、LinkedHashMap のキャストがうまくいかないようです。

javaの経験が乏しく具体的に何をどうしたら解決することが出来るのか分かりません。
詳しい方がいらしたら、アドバイスをお願いいたします。

なお、instagramのレスポンスを一部抜粋した内容は以下のとおりです。

  "data": [
    {
      "attribution": null,
      "tags": [
        "北欧",
        "chihuahua",
        "チワワ",
        "熊",
        "dog",
        "bear",
        "クマ",
        "pet",
        "インテリア",
        "犬との暮らし",
        "マンション",
        "ビーズクッション",
        "暮らし",
        "愛犬"
      ],
      "type": "image",
      "location": null,
     "comments": {
        "count": 1,
        "data": [
          {
            "created_time": "1446716528",
        ~

以下呼び出し元

    public クラスA Access(String url) throws IOException {
        クラスA instagramData;
        try {
            HttpRequestFactory requestFactory = httpTransport.createRequestFactory();
            GenericUrl genericUrl = new GenericUrl(url);
            HttpRequest req = requestFactory.buildGetRequest(genericUrl);
            HttpResponse res = req.execute();

            try {
                instagramData = asInstagramObject(res.parseAsString());
            } finally {
                res.disconnect();
            }
        } finally {
            httpTransport.shutdown();
        }
        return instagramData;
    }

    // Json文字列をInstagramObjectに変換
    private クラスA asInstagramObject(String stringJson) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        クラスA json = mapper.readValue(stringJson, クラスA.class);
        return json;
    }
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • swordone

    2015/11/12 10:27

    DataBreakDownクラスの内容と、拡張for文の後に何を返しているのか(つまり、何を返すメソッドの中に書いているのか)、またそのメソッドはどこに属しているのかなどが不明です。省略しているメソッドを可能な限り書いてください。

    キャンセル

  • rontec

    2015/11/12 10:49

    ご返信ありがとうございます。
    省略していたコードを追加させて頂きました。
    もし何かお気づきの点があれば、ご教示いただければ幸いです。

    キャンセル

回答 3

checkベストアンサー

+3

HTTP Client Library for Javaの1.20.0で試したところ、以下のコードでうまくいきました。

変更点は
 1.@Getterを削除した。
 2.自分でFactoryを作った。
 3.class DataBreakdownpublic static class DataBreakdownに直した。
です。

Factoryを自分で作って実行していることもあってか、
同じ現象は発生しませんでしたが、少なくとも
3.を実施しないと、例外が発生して動作しませんでした。

import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.client.util.Key;

public class クラスA extends GenericJson {
    @Key("data")
    private List<DataBreakdown> data;

    // inner class
    public static class DataBreakdown {
        @Key("attribution")
        private Object attribution;
        @Key("tags")
        private List<String> tsgs;
        @Key("type")
        private String type;
        @Key("location")
        private Object location;
    }

    public List<HashMap<String, Object>> getInstagramImageData() {
        List<HashMap<String, Object>> instagramImageList = new ArrayList<>();

        for (DataBreakdown oneData : data) {
            System.out.println(oneData.type);

        }
        return instagramImageList;
    }

    public static void main(String[] args) {
        JacksonFactory factory = new JacksonFactory();
        JsonObjectParser objectParser = factory.createJsonObjectParser();
        try {
            クラスA a = objectParser.parseAndClose(new FileReader("input.json"), クラスA.class);
            a.getInstagramImageData();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

input.jsonの中身
{
  "data": [
    {
      "attribution": null,
      "tags": [
        "北欧"
      ],
      "type": "image",
      "location": null
    },
    {
      "attribution": null,
      "tags": [
        "北欧"
      ],
      "type": "image",
      "location": null
    }
  ]    
}

推測される原因

おそらく、ライブラリ側からパース時にDataBreakdownなどのインスタンスをnewするために、
ライブラリからアクセス可能なデフォルトコンストラクタが必要なことが原因です。
public staticを付けない場合に、以下のような例外が発生したためです。
試してはいませんが、内部クラスをやめて、publicなクラスにしても成功すると思います。
Exception in thread "main" java.lang.IllegalArgumentException: 
    at com.google.api.client.json.JsonParser.parseValue(JsonParser.java:880)
    at com.google.api.client.json.JsonParser.parse(JsonParser.java:381)
    at com.google.api.client.json.JsonParser.parse(JsonParser.java:354)
    at com.google.api.client.json.JsonObjectParser.parseAndClose(JsonObjectParser.java:98)
    at com.google.api.client.json.JsonObjectParser.parseAndClose(JsonObjectParser.java:92)
    at sandbox.httpclient.クラスA.main(クラスA.java:46)
-(略)-
Caused by: java.lang.IllegalArgumentException: unable to create new instance of class sandbox.httpclient.クラスA$DataBreakdown because it is not static and possibly because it is not public
    at com.google.api.client.util.Types.handleExceptionForNewInstance(Types.java:165)
    at com.google.api.client.util.Types.newInstance(Types.java:120)
    at com.google.api.client.json.JsonParser.parseValue(JsonParser.java:763)
    ... 9 more
-(略)-

 [追記]実際にリクエストからパースする場合

以下のコードで、実際にHTTPリクエストを送って結果をパースすることができました。
変更点は、
1.Factoryを使ってJsonObjectParserを追加した。
2.asInstagramObject(res.parseAsString())していたのを、parseAs(クラスA.class)に変更した。
3.動作確認のためにinstagramData.getInstagramImageData();を追加した。

try {
    HttpRequestFactory requestFactory = httpTransport.createRequestFactory();
    GenericUrl genericUrl = new GenericUrl(url);
    HttpRequest req = requestFactory.buildGetRequest(genericUrl);
    req.setParser(new JacksonFactory().createJsonObjectParser());  // 1.追加
    HttpResponse res = req.execute();

    try {
        instagramData = res.parseAs(クラスA.class); // 2.変更
        instagramData.getInstagramImageData(); // 3.動作確認のために追加
    } finally {
        res.disconnect();
    }
} finally {
    httpTransport.shutdown();
}

動作確認に使ったjarは、以下の通りです。
commons-logging-1.1.1.jar
google-http-client-1.20.0.jar
google-http-client-jackson-1.20.0.jar
lombok.jar
jackson-core-asl-1.9.11.jar

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/11/12 20:15

    こちらで変更して、動作はするようになったコードを、回答に追記しました。

    リクエストをStringに読み込んでObjectMapperでパースするのではなく、
    HTTP Client Library for Javaの機能だけで実現しましたが、
    ObjectMapperを使う必要はありますか?
    そもそも、ObjectMapperがどのライブラリのクラスか分かりませんでした。

    キャンセル

  • 2015/11/12 22:09

    使用したライブラリも記載するべきでした、失礼いたしました。

    色々ご相談に乗って頂き、本当にありがとうございます。
    ご指摘の通り実装したところ、無事キャストして実行することが出来ました。

    ObjectMapperを使用した理由は、parseAsを使用してもうまくいかず、parseAsStringをとObjectMapperを使用したサイトの方法を真似たところ、とりあえず動いたのでそのままにしたという、お恥ずかしいことにあまり理解もせず使用していた部分です。

    1からJavaのコードを作成するのは実に9年ぶりなので不明点も多く、今回の件も事故解決出来ずteratailで相談させて頂いておりました。

    ありがとうございました。

    キャンセル

  • 2015/11/12 22:16

    9年前だと、Javaもだいぶ違いますよね。
    解決してよかったです。

    キャンセル

+1

その拡張forでその例外が出ているとしたら、考えられる可能性は、
List<DataBreakdown> dataの要素に、実体がLinkedHashMapのオブジェクトが入っている、ということだと思われます。
このdataをどうやって生成しているのかわかりませんが、
APIで型を指定しないListを作ったとして、それをList<DataBreakdown>に代入してもコンパイルエラーにはなりません。Javaの総称型の特性上、こういうことが可能になってしまっています。
dataが持つListは型をDataBreakdownで指定しているため、取得などの時に要素をDataBreakdownにキャストします。このとき、要素の実体が実はLinkedHashMapであった場合、提示されたような例外が発生します。
このAPIの事をよく知らないので、もう一度検討しなおしたほうがいいとしかアドバイスできないのが辛いです…

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/11/12 11:30

    はい、どうやらinstagramのdataの中身がLinkedHashMapになっているようで、どうにかこれをjavaで扱い易い形に変更したいと考えております。

    レスポンスの中身が例えば<String,String>で一定であれば問題ないのですが、keyによっては階層が深いので、こちらでクラスを用意せざる得ないところです。
    現状フロントのJSに投げた後、JSであれば連想配列として簡単に扱えるので対応できているのですが、javaとしてこのような際どう扱えばよいか知っておきたいと思い、今回質問させて頂いておりました。

    ご対応ありがとうございました。
    ご指摘からもDataBreakdownの定義方法がやはりよろしくないことがわかりました。

    キャンセル

  • 2015/11/12 11:47

    JavaScriptの連想配列もJavaのMapも基本的な発想は同じで、何かを鍵として与えるとそれに関連づいた物が取得できる、という点で共通しています。
    おそらくdataに入っている本来の形はList<LinkedHashMap<String, Object>>なのではないでしょうか?
    そうだと仮定して、分解する際には、キーによって関連づいている型がある程度決まっていると思われるので、キーを使ってObjectを取得した後、値をキーをもとにキャストすればいいのではないでしょうか?
    例えばレスポンスでtagsというキーにはList<String>が対応し、commentsにはMap<String, Object>が対応していると思われます。それぞれにあったキャストを適用して値を分解し、格納していくのがいいのではないでしょうか?
    あくまで推測ですし、かなり面倒そうですが。

    キャンセル

  • 2015/11/12 11:52

    rontecさんとswordoneさんで認識があっていない気がします。

    for (DataBreakdown oneData : data) {
    この部分のdataの型がおかしいのではないか?と疑っているので、
    ブレークポイントをつけてデバッグモードでdataを確認してみてはいかがですか?

    キャンセル

  • 2015/11/12 15:53 編集

    ご指摘ありがとうございます。
    dataの中身は下記のような、LinkedHashMapになっております。

    argius様への返信にも書いたのですが、@keyアノテーションを調べたところHTTP Client Library for Javaで通信したレスポンスをパースする際にkeyで指定した値を拾うようなので、dataで一度パースされていたことが原因で機能しなかったのではないかと推測しております。

    ```java
    {
    attribution=null,
    tags=[rabbits, triu?is, haas, coello, lucky, likes, rabbit, follow, conill, ??, like, kanin, okeoyibo, follows, 兔仔, 兔子, terwelu, tav?an, ウサギ, трус?к, 兔],
    type=image,
    location=null,
    comments={count=0, data=[]},
    filter=Normal,
    created_time=1446773288,
    link=https://,
    likes={count=1,
    data=[{username=homey_the_bunny, profile_picture=http://, id=XXXXXXXXX, full_name=Ho Mey Lee}]},
    images={
    low_resolution={url=XXXXXXXXXXX, width=320, height=320},
    thumbnail={url=XXXXXXXXXXXXX, width=150, height=150},
    standard_resolution={url=XXXXXXXXXXX, width=640, height=640}
    },
    users_in_photo=[],
    caption={
    created_time=1446773288,
    text=?lucky : good morning ?#rabbit #rabbits #lucky #ウサギ #?? #兔 #兔子 #兔仔 #follow #follows #like #likes #Tav?an #Kanin #Terwelu #Coello #Conill #Haas #Трус?к #Triu?is #OkeOyibo,
    from={username=rabbit_lucky_,
    profile_picture=XXXXXXXXXXXXx, id=XXXXXX,
    full_name=?Lucky?
    },
    id=XXXXXXXXXXXXXXXXX,
    user_has_liked=false,
    id=XXXXXXXXXXXXXXXX,
    user={
    username=rabbit_lucky_,
    profile_picture=XXXXXXXXXXXXXXXXXX,
    id=XXXXXXXXXXXX,
    full_name=?Lucky?
    }
    }
    ```

    ※公開されているので問題ないとは思うのですが、一応該当instagramデータが特定出来そうな部分は潰しております。

    キャンセル

0

良い方法とは思えませんが、欲しい項目を一つ一つputする方法を取りました。
欲しい項目の階層が深いとき、キャストにキャストを重ねているため褒められた方法でないのは確かです。
こういう時、ほんとどうしたら良いのでしょうね。

        for (HashMap<String, Object> oneData : data) {
            HashMap<String, Object> castData = new HashMap<>();
            castData.put("tags", oneData.get("tags"));
            castData.put("type", oneData.get("type"));
            castData.put("link", oneData.get("link"));
            castData.put("likes_count", ((HashMap<String, Object>)oneData.get("likes")).get("count"));
            castData.put("images_url_low", ((HashMap<String, HashMap<String, String>>)oneData.get("images")).get("low_resolution").get("url"));
            castData.put("images_url_standard", ((HashMap<String, HashMap<String, String>>)oneData.get("images")).get("standard_resolution").get("url"));
            castData.put("caption", oneData.get("caption"));
            castData.put("id", oneData.get("id"));
//            castData.put("user", oneData.get("user"));
            instagramImageList.add(castData);
        }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/11/12 13:24

    HTTP Client Libraryってこれですか?→google-http-java-client
    このページ
    tree-tips: google-http-java-clientで簡単httpリクエスト! | java
    http://www.tree-tips.com/java/googleHttpJavaClient/
    を見る限りでは、ちゃんとマッピングできているように見えます。
    どのような情報を参考に作られたのでしょうか?
    それが間違っている可能性はありませんか?

    キャンセル

  • 2015/11/12 14:43

    書き込みありがとうございます。
    はい、貼っていただいたURLのもので間違いございません。
    私も言語を問わず様々なサイトを参照したので一概に、このページですと言えないのですが、このページも参考にしたことがありますが、うまくいきませんでした。

    おそらくですが、instagramのレスポンスを正しくキャスト出来るようなクラスを私が作れていないのだと思います。
    また当初の質問に関しては、@Keyのアノテーションを調べたところ、パース後にキャストするようなことが書いてあったので、一度dataでパースした後の中身を更に@keyを使用しても意味なかったのではと考えております。

    キャンセル

  • 2015/11/12 17:18

    返信ありがとうございます。
    私も興味があるので調べてみようと思いましたが、eripongさんの回答が参考になりそうなので、そちらにお任せします。

    キャンセル

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

  • ただいまの回答率 88.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る