Home > database >  parse No Key json list with moshi in Android
parse No Key json list with moshi in Android

Time:07-03

What I am trying to do is to get Json response from GitHub API call of user's repository list.

response example from Github is like below.

[
  {
    "id": 78688034,
    "node_id": "MDEwOlJlcG9zaXRvcnk3ODY4ODAzNA==",
    "name": "py3status",
    "full_name": "randomguy/py3status",
    "private": false,
    "owner": {
      "login": "randomguy",
      "id": 798223,
      "node_id": "MDQ6VXNlcjc5ODIyMw==",
      "avatar_url": "https://avatars.githubusercontent.com/u/798223?v=4",
      "gravatar_id": "",
      "url": "https://api.github.com/users/randomguy",
      "html_url": "https://github.com/randomguy",
      "followers_url": "https://api.github.com/users/randomguy/followers",
      "following_url": "https://api.github.com/users/randomguy/following{/other_user}",
      "gists_url": "https://api.github.com/users/randomguy/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/randomguy/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/randomguy/subscriptions",
      "organizations_url": "https://api.github.com/users/randomguy/orgs",
      "repos_url": "https://api.github.com/users/randomguy/repos",
      "events_url": "https://api.github.com/users/randomguy/events{/privacy}",
      "received_events_url": "https://api.github.com/users/randomguy/received_events",
      "type": "User",
      "site_admin": false
    },
    "html_url": "https://github.com/randomguy/py3status",
    "description": "py3status is an extensible i3status wrapper written in python",
    "fork": true,
    "url": "https://api.github.com/repos/randomguy/py3status",
    "forks_url": "https://api.github.com/repos/randomguy/py3status/forks",
    "keys_url": "https://api.github.com/repos/randomguy/py3status/keys{/key_id}",
    "collaborators_url": "https://api.github.com/repos/randomguy/py3status/collaborators{/collaborator}",
    "teams_url": "https://api.github.com/repos/randomguy/py3status/teams",
    "hooks_url": "https://api.github.com/repos/randomguy/py3status/hooks",
    "issue_events_url": "https://api.github.com/repos/randomguy/py3status/issues/events{/number}",
    "events_url": "https://api.github.com/repos/randomguy/py3status/events",
    "assignees_url": "https://api.github.com/repos/randomguy/py3status/assignees{/user}",
    "branches_url": "https://api.github.com/repos/randomguy/py3status/branches{/branch}",
    "tags_url": "https://api.github.com/repos/randomguy/py3status/tags",
    "blobs_url": "https://api.github.com/repos/randomguy/py3status/git/blobs{/sha}",
    "git_tags_url": "https://api.github.com/repos/randomguy/py3status/git/tags{/sha}",
    "git_refs_url": "https://api.github.com/repos/randomguy/py3status/git/refs{/sha}",
    "trees_url": "https://api.github.com/repos/randomguy/py3status/git/trees{/sha}",
    "statuses_url": "https://api.github.com/repos/randomguy/py3status/statuses/{sha}",
    "languages_url": "https://api.github.com/repos/randomguy/py3status/languages",
    "stargazers_url": "https://api.github.com/repos/randomguy/py3status/stargazers",
    "contributors_url": "https://api.github.com/repos/randomguy/py3status/contributors",
    "subscribers_url": "https://api.github.com/repos/randomguy/py3status/subscribers",
    "subscription_url": "https://api.github.com/repos/randomguy/py3status/subscription",
    "commits_url": "https://api.github.com/repos/randomguy/py3status/commits{/sha}",
    "git_commits_url": "https://api.github.com/repos/randomguy/py3status/git/commits{/sha}",
    "comments_url": "https://api.github.com/repos/randomguy/py3status/comments{/number}",
    "issue_comment_url": "https://api.github.com/repos/randomguy/py3status/issues/comments{/number}",
    "contents_url": "https://api.github.com/repos/randomguy/py3status/contents/{ path}",
    "compare_url": "https://api.github.com/repos/randomguy/py3status/compare/{base}...{head}",
    "merges_url": "https://api.github.com/repos/randomguy/py3status/merges",
    "archive_url": "https://api.github.com/repos/randomguy/py3status/{archive_format}{/ref}",
    "downloads_url": "https://api.github.com/repos/randomguy/py3status/downloads",
    "issues_url": "https://api.github.com/repos/randomguy/py3status/issues{/number}",
    "pulls_url": "https://api.github.com/repos/randomguy/py3status/pulls{/number}",
    "milestones_url": "https://api.github.com/repos/randomguy/py3status/milestones{/number}",
    "notifications_url": "https://api.github.com/repos/randomguy/py3status/notifications{?since,all,participating}",
    "labels_url": "https://api.github.com/repos/randomguy/py3status/labels{/name}",
    "releases_url": "https://api.github.com/repos/randomguy/py3status/releases{/id}",
    "deployments_url": "https://api.github.com/repos/randomguy/py3status/deployments",
    "created_at": "2017-01-11T23:05:15Z",
    "updated_at": "2017-01-11T23:05:17Z",
    "pushed_at": "2017-08-04T18:56:56Z",
    "git_url": "git://github.com/randomguy/py3status.git",
    "ssh_url": "[email protected]:randomguy/py3status.git",
    "clone_url": "https://github.com/randomguy/py3status.git",
    "svn_url": "https://github.com/randomguy/py3status",
    "homepage": "",
    "size": 3078,
    "stargazers_count": 0,
    "watchers_count": 0,
    "language": "Python",
    "has_issues": false,
    "has_projects": true,
    "has_downloads": true,
    "has_wiki": true,
    "has_pages": false,
    "forks_count": 0,
    "mirror_url": null,
    "archived": false,
    "disabled": false,
    "open_issues_count": 0,
    "license": {
      "key": "bsd-3-clause",
      "name": "BSD 3-Clause \"New\" or \"Revised\" License",
      "spdx_id": "BSD-3-Clause",
      "url": "https://api.github.com/licenses/bsd-3-clause",
      "node_id": "MDc6TGljZW5zZTU="
    },
    "allow_forking": true,
    "is_template": false,
    "web_commit_signoff_required": false,
    "topics": [

    ],
    "visibility": "public",
    "forks": 0,
    "open_issues": 0,
    "watchers": 0,
    "default_branch": "master"
  }
]

As you can see, the response is in arrayList form but with NO Key value.
Since this response doesn't have Key value for Json List, auto generated data classes look like these below(UserRepoListResponse and UserRepoListResponseItem).

class UserRepoListResponse : ArrayList<UserRepoListResponseItem>()

@JsonClass(generateAdapter = true)
data class UserRepoListResponseItem(
    @Json(name ="allow_forking") val allowForking: Boolean,
    @Json(name ="archive_url") val archiveUrl: String,
    @Json(name ="archived") val archived: Boolean,
    @Json(name ="assignees_url") val assigneesUrl: String,
    @Json(name ="blobs_url") val blobsUrl: String,
    @Json(name ="branches_url") val branchesUrl: String,
    @Json(name ="clone_url") val cloneUrl: String,
    @Json(name ="collaborators_url") val collaboratorsUrl: String,
    @Json(name ="comments_url") val commentsUrl: String,
    @Json(name ="commits_url") val commitsUrl: String,
    @Json(name ="compare_url") val compareUrl: String,
    @Json(name ="contents_url") val contentsUrl: String,
    @Json(name ="contributors_url") val contributorsUrl: String,
    @Json(name ="created_at") val createdAt: String,
    @Json(name ="default_branch") val defaultBranch: String,
    @Json(name ="deployments_url") val deploymentsUrl: String,
    @Json(name ="description") val description: String,
    @Json(name ="disabled") val disabled: Boolean,
    @Json(name ="downloads_url") val downloadsUrl: String,
    @Json(name ="events_url") val eventsUrl: String,
    @Json(name ="fork") val fork: Boolean,
    @Json(name ="forks") val forks: Int,
    @Json(name ="forks_count") val forksCount: Int,
    @Json(name ="forks_url") val forksUrl: String,
    @Json(name ="full_name") val fullName: String,
    @Json(name ="git_commits_url") val gitCommitsUrl: String,
    @Json(name ="git_refs_url") val gitRefsUrl: String,
    @Json(name ="git_tags_url") val gitTagsUrl: String,
    @Json(name ="git_url") val gitUrl: String,
    @Json(name ="has_downloads") val hasDownloads: Boolean,
    @Json(name ="has_issues") val hasIssues: Boolean,
    @Json(name ="has_pages") val hasPages: Boolean,
    @Json(name ="has_projects") val hasProjects: Boolean,
    @Json(name ="has_wiki") val hasWiki: Boolean,
    @Json(name ="homepage") val homepage: String,
    @Json(name ="hooks_url") val hooksUrl: String,
    @Json(name ="html_url") val htmlUrl: String,
    @Json(name ="id") val id: Int,
    @Json(name ="is_template") val isTemplate: Boolean,
    @Json(name ="issue_comment_url") val issueCommentUrl: String,
    @Json(name ="issue_events_url") val issueEventsUrl: String,
    @Json(name ="issues_url") val issuesUrl: String,
    @Json(name ="keys_url") val keysUrl: String,
    @Json(name ="labels_url") val labelsUrl: String,
    @Json(name ="language") val language: String,
    @Json(name ="languages_url") val languagesUrl: String,
    @Json(name ="license") val license: License,
    @Json(name ="merges_url") val mergesUrl: String,
    @Json(name ="milestones_url") val milestonesUrl: String,
    @Json(name ="mirror_url") val mirrorUrl: Any,
    @Json(name ="name") val name: String,
    @Json(name ="node_id") val nodeId: String,
    @Json(name ="notifications_url") val notificationsUrl: String,
    @Json(name ="open_issues") val openIssues: Int,
    @Json(name ="open_issues_count") val openIssuesCount: Int,
    @Json(name ="owner") val owner: Owner,
    @Json(name ="private") val privateValue: Boolean,
    @Json(name ="pulls_url") val pullsUrl: String,
    @Json(name ="pushed_at") val pushedAt: String,
    @Json(name ="releases_url") val releasesUrl: String,
    @Json(name ="size") val size: Int,
    @Json(name ="ssh_url") val sshUrl: String,
    @Json(name ="stargazers_count") val stargazersCount: Int,
    @Json(name ="stargazers_url") val stargazersUrl: String,
    @Json(name ="statuses_url") val statusesUrl: String,
    @Json(name ="subscribers_url") val subscribersUrl: String,
    @Json(name ="subscription_url") val subscriptionUrl: String,
    @Json(name ="svn_url") val svnUrl: String,
    @Json(name ="tags_url") val tagsUrl: String,
    @Json(name ="teams_url") val teamsUrl: String,
    @Json(name ="topics") val topics: List<String>,
    @Json(name ="trees_url") val treesUrl: String,
    @Json(name ="updated_at") val updatedAt: String,
    @Json(name ="url") val url: String,
    @Json(name ="visibility") val visibility: String,
    @Json(name ="watchers") val watchers: Int,
    @Json(name ="watchers_count") val watchersCount: Int
)

I am using Retofit2 and Moshi. Dependencies are like below

// retrofit moshi
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"

//Moshi Library Dependencies - Core Moshi JSON Library and Moshi's Kotlin support and converter factory
implementation "com.squareup.moshi:moshi:1.13.0"
implementation "com.squareup.moshi:moshi-kotlin:1.13.0"

and this is how I set up Moshi and Retrofit2

val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

private val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .baseUrl(BASE_URL)
    .client(getHttpClient())
    .build()

and this is my api call function

@GET("/users/{username}/repos")
suspend fun getUserRepos(
    @Path("username") username: String,
): UserRepoListResponse

then I call the API call, I get this error

Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $

what am I doing wrong? please help me..

CodePudding user response:

Because the JSON you are getting is an array, you need to return an array (List) type instead of an object type. The error came from Moshi trying to parse an array (outer tokens []) as an object (outer tokens {}).

Change this:

@GET("/users/{username}/repos")
suspend fun getUserRepos(
    @Path("username") username: String,
): UserRepoListResponse

to return a list instead

@GET("/users/{username}/repos")
suspend fun getUserRepos(
    @Path("username") username: String,
): List<UserRepoListResponseItem>
  • Related