0

I want to parse a JSON using Java and I managed to do this for the case when I get a response with values from the GET call. My problem comes when the response comes with no values because I get the exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of com.footbal.dtoStatistics.Statistics out of START_ARRAY token at

[Source: (String)"{"api":{"results":0,"statistics":[]}}"; line: 1, column: 34] (through reference chain: com.footbal.dtoStatistics.StatisticsResponse["api"]->com.footbal.dtoStatistics.Api["statistics"])

My JSON without values looks like this:

{
    "api": {
        "results": 0,
        "statistics": []
    }
}

My Json with values for which the parsing works looks like this:

{
    "api": {
        "results": 16,
        "statistics": {
            "Shots on Goal": {
                "home": "3",
                "away": "9"
            },
            "Shots off Goal": {
                "home": "5",
                "away": "3"
            },
            "Total Shots": {
                "home": "11",
                "away": "16"
            },
            "Blocked Shots": {
                "home": "3",
                "away": "4"
            },
            "Shots insidebox": {
                "home": "4",
                "away": "14"
            },
            "Shots outsidebox": {
                "home": "7",
                "away": "2"
            },
            "Fouls": {
                "home": "10",
                "away": "13"
            },
            "Corner Kicks": {
                "home": "7",
                "away": "4"
            },
            "Offsides": {
                "home": "2",
                "away": "1"
            },
            "Ball Possession": {
                "home": "55%",
                "away": "45%"
            },
            "Yellow Cards": {
                "home": "0",
                "away": "2"
            },
            "Red Cards": {
                "home": null,
                "away": null
            },
            "Goalkeeper Saves": {
                "home": "7",
                "away": "1"
            },
            "Total passes": {
                "home": "543",
                "away": "436"
            },
            "Passes accurate": {
                "home": "449",
                "away": "355"
            },
            "Passes %": {
                "home": "83%",
                "away": "81%"
            }
        }
    }
}

And the classes I used for the parsing are:

public class StatisticsResponse {

    Api api;

    public Api getApi() {
        return api;
    }

    public void setApi(Api api) {
        this.api = api;
    }
}
public class Api {

    int results;
    Statistics statistics;

    public int getResults() {
        return results;
    }

    public void setResults(int results) {
        this.results = results;
    }

    public Statistics getStatistics() {
        return statistics;
    }

    public void setStatistics(Statistics statistics) {
        this.statistics = statistics;
    }
}
@JsonAutoDetect(
        fieldVisibility = JsonAutoDetect.Visibility.ANY,
        getterVisibility = JsonAutoDetect.Visibility.NONE,
        setterVisibility = JsonAutoDetect.Visibility.NONE)
public class Statistics {

    @JsonProperty ("Shots on Goal")
    Stats ShotsonGoal;

    @JsonProperty ("Shots off Goal")
    Stats ShotsoffGoal;

    @JsonProperty ("Total Shots")
    Stats TotalShots;

    @JsonProperty ("Blocked Shots")
    Stats BlockedShots;

    @JsonProperty ("Shots insidebox")
    Stats Shotsinsidebox;

    @JsonProperty ("Shots outsidebox")
    Stats Shotsoutsidebox;
    Stats Fouls;

    @JsonProperty ("Corner Kicks")
    Stats CornerKicks;
    Stats Offsides;

    @JsonProperty ("Ball Possession")
    StatsPercent BallPossesion;

    @JsonProperty ("Yellow Cards")
    Stats YellowCards;

    @JsonProperty ("Red Cards")
    Stats RedCards;

    @JsonProperty ("Goalkeeper Saves")
    Stats GoalkeeperSaves;

    @JsonProperty ("Total passes")
    Stats Totalpasses;

    @JsonProperty ("Passes accurate")
    Stats Passesaccurate;

    @JsonProperty("Passes %")
    StatsPercent Passes;

    public Stats getShotsonGoal() {
        return ShotsonGoal;
    }

    public void setShotsonGoal(Stats shotsonGoal) {
        ShotsonGoal = shotsonGoal;
    }

    public Stats getShotsoffGoal() {
        return ShotsoffGoal;
    }

    public void setShotsoffGoal(Stats shotsoffGoal) {
        ShotsoffGoal = shotsoffGoal;
    }

    public Stats getTotalShots() {
        return TotalShots;
    }

    public void setTotalShots(Stats totalShots) {
        TotalShots = totalShots;
    }

    public Stats getBlockedShots() {
        return BlockedShots;
    }

    public void setBlockedShots(Stats blockedShots) {
        BlockedShots = blockedShots;
    }

    public Stats getShotsinsidebox() {
        return Shotsinsidebox;
    }

    public void setShotsinsidebox(Stats shotsinsidebox) {
        Shotsinsidebox = shotsinsidebox;
    }

    public Stats getShotsoutsidebox() {
        return Shotsoutsidebox;
    }

    public void setShotsoutsidebox(Stats shotsoutsidebox) {
        Shotsoutsidebox = shotsoutsidebox;
    }

    public Stats getFouls() {
        return Fouls;
    }

    public void setFouls(Stats fouls) {
        Fouls = fouls;
    }

    public Stats getCornerKicks() {
        return CornerKicks;
    }

    public void setCornerKicks(Stats cornerKicks) {
        CornerKicks = cornerKicks;
    }

    public Stats getOffsides() {
        return Offsides;
    }

    public void setOffsides(Stats offsides) {
        Offsides = offsides;
    }

    public StatsPercent getBallPossesion() {
        return BallPossesion;
    }

    public void setBallPossesion(StatsPercent ballPossesion) {
        BallPossesion = ballPossesion;
    }

    public Stats getYellowCards() {
        return YellowCards;
    }

    public void setYellowCards(Stats yellowCards) {
        YellowCards = yellowCards;
    }

    public Stats getRedCards() {
        return RedCards;
    }

    public void setRedCards(Stats redCards) {
        RedCards = redCards;
    }

    public Stats getGoalkeeperSaves() {
        return GoalkeeperSaves;
    }

    public void setGoalkeeperSaves(Stats goalkeeperSaves) {
        GoalkeeperSaves = goalkeeperSaves;
    }

    public Stats getTotalpasses() {
        return Totalpasses;
    }

    public void setTotalpasses(Stats totalpasses) {
        Totalpasses = totalpasses;
    }

    public Stats getPassesaccurate() {
        return Passesaccurate;
    }

    public void setPassesaccurate(Stats passesaccurate) {
        Passesaccurate = passesaccurate;
    }

    public StatsPercent getPasses() {
        return Passes;
    }

    public void setPasses(StatsPercent passes) {
        Passes = passes;
    }
}
try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.registerModule(new JavaTimeModule());
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            StatisticsResponse apiResponse = mapper.readValue(statisticsResponse, StatisticsResponse.class);


                statisticsList = apiResponse.getApi().getStatistics();

Why would it work for the case with values and for the case when I get no values it fails? I cannot see what I did wrong.

Can anyone help me with this parsing?

dawis11
  • 820
  • 1
  • 9
  • 24
Maria1995
  • 439
  • 1
  • 5
  • 21
  • The problem is in the code generating the JSON. When you don't have any values, it provides an array (`[]`) for `api.statistics`, but when there are values, it provides an object (`{...}`). It should **always** provide an object, even when there are no values: `{}`. Alternatively, it should provide `null`. But providing `[]` in one case and `{...}` in another is...not best practice. :-) – T.J. Crowder Apr 26 '20 at 11:23
  • and what can I do ? how should my code deal with this? because I cannot change the way the API looks – Maria1995 Apr 26 '20 at 11:48
  • Hi @Maria1995, the solution to that issue is quite simple - make `statistics` untyped that is change its type to Object, please check [the answer](https://stackoverflow.com/a/61583743/13279831) for details. – Nowhere Man May 04 '20 at 01:18

2 Answers2

0

In your case, Json with values and without values are different. Without values, your JSON is having Statistics as a List. While the other case, it is not a List. Your classes satisfy the second case where there are values. When there are no values, class Api should contain List<Statistics> instead of Statistics.

Ideally, try to follow same structure for both cases. If Statistics never will be list, then change:

{
    "api": {
        "results": 0,
        "statistics": []
    }
}

to

{
    "api": {
        "results": 0,
        "statistics": {}
    }
}

Else if Statistics will return List, change

{
    "api": {
        "results": 16,
        "statistics": {
            "Shots on Goal": {
                "home": "3",
                "away": "9"
            },
            "Shots off Goal": {
                "home": "5",
                "away": "3"
            },...
        }}}

to

{
    "api": {
        "results": 16,
        "statistics": [{
            "Shots on Goal": {
                "home": "3",
                "away": "9"
            },
            "Shots off Goal": {
                "home": "5",
                "away": "3"
            },...
        }]
      }}
Nandu Raj
  • 2,072
  • 9
  • 20
  • The thing is that I cannot change the API...I am just a user of it. How should my code deal with this situation? – Maria1995 Apr 26 '20 at 11:49
0

A solution to this issue is quite simple: change type of statistics to Object and update getter/setter and untyped mapping will work fine.

class Api {
    int results;
    Object statistics;

    public int getResults() {
        return results;
    }

    public void setResults(int results) {
        this.results = results;
    }

    public Object getStatistics() {
        return statistics;
    }

    public void setStatistics(Object statistics) {
        this.statistics = statistics;
    } 
}

Test code produces expected output:

String emptyStats = "{\n" + 
            "    \"api\": {\n" + 
            "        \"results\": 0,\n" + 
            "        \"statistics\": []\n" + 
            "     }\n" + 
            "}";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//StatisticsResponse apiResponse = mapper.readValue(json, StatisticsResponse.class);
StatisticsResponse apiResponse = mapper.readValue(emptyStats, StatisticsResponse.class);

String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(apiResponse);
System.out.println(json);

// ----------------
{
  "api" : {
    "results" : 0,
    "statistics" : [ ]
  }
}
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42