当内部 json object 有条件地为空时,使用 Jackson 将 JSON object 映射到 Java

Mapping a JSON object to Java using Jackson when an inner json object is conditionally empty

我正在为 musixmatch.com 公开的 API 实现一个 REST 客户端(根据一些参数搜索歌词,例如:歌曲名称、艺术家等)。 (REST/JSON 的新手总数)。

我的客户代码:

import java.io.IOException;
import java.io.InputStream;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.MappingJsonFactory;

import in.soumav.musixmatch.beans.MMResponse;

public class MusixMatchClient {

    public static void main(String[] args) throws JsonParseException, IOException {
        Client client = ClientBuilder.newClient();
        WebTarget target = client.target("https://api.musixmatch.com").path("ws").path("1.1")
                                        .path("matcher.lyrics.get")
                                        .queryParam("q_track", "Wake Me Up")
                                        .queryParam("q_artist", "Greenday")
                                        .queryParam("apikey", "...my key...");
        Builder builderRequest = target.request();
        Response response = builderRequest.get();
        if (Response.Status.OK.getStatusCode() == response.getStatus()) {
           MappingJsonFactory factory = new MappingJsonFactory();
           JsonParser parser = factory.createJsonParser((InputStream) response.getEntity());
           MMResponse resp = parser.readValueAs(MMResponse.class);
           System.out.println(resp.getMessage().getBody().getLyrics().getLyrics_body());
       }

    }
}

JSON 解析器在找到匹配时解析得很好。

找到匹配项时 API(在 POSTMAN 中)的响应:

{
    "message": {
        "header": {
            "status_code": 200,
            "execute_time": 0.076157093048096
        },
        "body": {
            "lyrics": {
                "lyrics_id": 14080789,
                "explicit": 0,
                "lyrics_body": "Summer has come and passed\nThe innocent can never last\nWake me up when September ends\n\nLike my father's come to pass\nSeven years has gone so fast\nWake me up when September ends\n\nHere comes the rain again\nFalling from the stars\nDrenched in my pain again\nBecoming who we are\n\nAs my memory rests\nBut never forgets what I lost\n...\n\n******* This Lyrics is NOT for Commercial use *******",
                "script_tracking_url": "https://tracking.musixmatch.com/t1.0/m_js/e_1/sn_0/l_14080789/su_0/rs_0/tr_3vUCADW8Npey61GB2HRIObKY4Sz3low2ucLdqisdi7cAswHSwWeSM5uhan_JCxhFMVX0wKQjr_6eciPf6twsVnnD4RPIGl8wpzGhgPLQRIkUkBP0Zga626PhvTK603MycNGrL9kMxuLM8iA8b8IYiLDV_WzIffNXs2ENvhz39AuqdldwE0H-mS51SpVUU21V--VBwXN1uBD3ra2GANeMeuvOMMP4-8sa1tE3FvaEVUaP-mqxgQtDJHG2_aY01nRNfzLPZ86xPscgeQMrPiILn5lpC7mkagXpQDqXo0MpoTnHZPFKqhrgiVNxkpZzX-oqam8DZflIgIe4zMtg2Y3QE8MvjE5vp500IZUOz-Q0GhVKfND5T6Yv3-jOuvkRkSxYbI8tJmRhbVe5XX3DATCdqAB7zYHrwyijL90yRJJzv2f-SC4E6f0J/",
                "pixel_tracking_url": "https://tracking.musixmatch.com/t1.0/m_img/e_1/sn_0/l_14080789/su_0/rs_0/tr_3vUCAIcjYkNNhw02X2Dtvu6SSWbscstTuW8YKvMwxRjtwZRmYXk4v0xsOdvjifHmsY2VY5yQTLHPUONXovxMI4XXZKXkC4KwjUwXR1afVXX5JE010OvQ-IsmsFkZ7-wXEXWJEFH5WJoKALES1HdQPDGsDNy6J5mGQURtG9MsQTSnfRnNz6zJkWTLZZ2F1GnG9f7ncW8guku-lOOWYAPcs8--4U4A3uR47_hn6PBh-JvHaQ2lFUjD6L0JuKs_Bgx0nU6RN97H02EkD6xkYWtsjZytPEneUHf3IrikZMu-tWvoclj0imoww8c_8NlqvHXG8pHYFO4YLPwk5kqfRXlW2TtwmJ9u1Do048_UFyCLAIctEEZBJEfcwjSep7VwU3BOiaKhL2Cwe-xV-NdW5pSzAbRb8bZ5PRrUnD7P-7SzdDSbossTyt6O/",
                "lyrics_copyright": "Lyrics powered by www.musixmatch.com. This Lyrics is NOT for Commercial use and only 30% of the lyrics are returned.",
                "updated_time": "2016-02-06T23:07:22Z"
            }
        }
    }
}

然而,当未找到匹配项时,将返回以下响应(通过 POSTMAN 进行测试时):

{"message":{"header":{"status_code":404,"execute_time":0.11416816711426},"body":[]}}

POSTMAN 中的实际状态码 returns 200 OK 虽然! 由于 body 等于一个空数组,JSON 解析器无法解析它并抛出以下异常:

Exception in thread "main" org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of in.soumav.musixmatch.beans.Body out of START_ARRAY token at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@d9345cd; line: 1, column: 74] (through reference chain: in.soumav.musixmatch.beans.MMResponse["message"]->in.soumav.musixmatch.beans.Message["body"])

我的 object 映射如下:

             MMResponse 
                 |
              Message
           ______|____________________
          |                           | 
         Header(status_cd,           Body
       execution_time)                |
                                      |
                                    Lyrics(lyrics_id,lyrics_body etc.)

问题:

  1. 什么是更好的 way/approach 实现此目标?
  2. 如果我必须对现有代码进行更改,我将如何处理?
  3. 如何在将 header(在 JSON 响应中)映射到 Java object 之前读取 status_code?

您从 API 收到的状态代码似乎与响应对象中的状态代码不匹配。因为这个响应不应该满足你的if条件 Response.Status.OK.getStatusCode() == response.getStatus()

{"message":{"header":{"status_code":404,"execute_time":0.11416816711426},"body":[]}}

如果 API 错误并且您想绕过它,则需要检查 JSON 响应中的 status_code。我会建议这样的东西:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;

...

if (Response.Status.OK.getStatusCode() == response.getStatus()) {
  ObjectMapper mapper = new ObjectMapper();
  // Validate object status status_code or message.header.status_code
  String jsonString = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());

  JsonNode jsonObject = mapper.readTree(jsonString);
  String statusCode =
          jsonObject.get("message")
                  .get("header")
                  .get("status_code")
                  .toString();

  if(statusCode.equalsIgnoreCase("200")) {
    MappingJsonFactory factory = new MappingJsonFactory();
    JsonParser parser = factory.createJsonParser((InputStream) response.getEntity());
    MMResponse resp = parser.readValueAs(MMResponse.class);
    System.out.println(resp.getMessage().getBody().getLyrics().getLyrics_body());  
  }
}

以上实现使用了 Jackson Object Mapper 和 commons IOUtils。 IOUtils 可以在下一个依赖项中找到:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.2</version>
</dependency>