1

I'm trying to parse a response JSON string from the server into my POJO objects using Gson. Below is my JSON:

{  
   "user":{  
      "id":"859adb60-4a47-4e1b-8f40-10480bfc33ec",
      "createdAt":"2017-05-11T07:52:43.661Z",
      "updatedAt":"2017-05-11T07:52:43.786Z",
      "version":"AAAAAAA0GXQ=",
      "deleted":false,
      "userName":null,
      "bucketId":"7c2b3ce4-ac5d-4e6a-a6c1-d6316c01fcfb",
      "userEmailId":"test1@tilicho.in",
      "signupType":"Custom",
      "userAvatar":null,
      "userPin":"$2a$10$9TCIqHrE6jDZMr0lhHRddeC7kU.Ob6s8o8zG3ahsmaAMBNi5Gkvwy",
      "verified":true,
      "verifyHash":"IpkzqvjHGb6nbdxxkvmgLiPJRSRhUJ6wMOO3V7Gz5ispOlpLPtdGMTkldzOwHp3Q",
      "thirdPartyToken":null,
      "loginAgent":null,
      "isDataSetUp":false
   },
   "colloboratedChildren":[  

   ],
   "invitations":[  
      {  
         "id":"4892167b-bc6a-47e5-bb30-1785b5643edc",
         "createdAt":"2017-05-11T07:52:06.019Z",
         "updatedAt":"2017-05-11T07:52:06.019Z",
         "version":"AAAAAAA0GWY=",
         "deleted":false,
         "inviterId":"9d00f972-e66d-400b-b332-f0b873a8b1fd",
         "inviterEmail":"nemani@tilicho.in",
         "inviteeId":null,
         "inviteeEmail":"test1@tilicho.in",
         "inviteData":"{\"821e63e7-457e-401f-b071-abacd51bcbad\":[\"B690E26C-BA39-45E3-BB34-CF6EABB666F7\"]}"
      }
   ],
   "authToken":"bQYng6AAeCp9HlR1rCZeOCbFUm1LaRfVzcqTWpgXiLul3tFj7Y4hKr997V6hvl6d"
}

Here are my POJO classes.

@Getter
@Setter
public class LoginResponse {

    User user;
    ArrayList<ColloboratedChildren> colloboratedChildren;
    ArrayList<InvitationResponse> invitations;
    String authToken;

}

@Getter
@Setter
public class User extends BucketAzureModel {

    private String signupType;
    private String userAvatar;
    private String userEmailId;
    private String userName;
    private String userPin;


}

@Getter
@Setter
ColloboratedChildren {}

public class InvitationResponse {

    String id;
    String createdAt;
    String updatedAt;
    String version;
    boolean deleted;
    String inviterId;
    String inviterEmail;
    String inviteeId;
    String inviteeEmail;
    Map<String, ArrayList<String>> inviteData;

}

The code that is converting the JSON to POJOs:

JsonEntityParser.parseResults(jsonElement, gsonBuilder.create(), concreteClass);

Here is the error I am facing:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at path $.invitations[0].inviteData
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:224)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
at com.google.gson.Gson.fromJson(Gson.java:887)
at com.google.gson.Gson.fromJson(Gson.java:952)
at com.google.gson.Gson.fromJson(Gson.java:925)
at com.microsoft.windowsazure.mobileservices.table.serialization.JsonEntityParser.parseResults(JsonEntityParser.java:64)

From the error I understand that Gson is expecting a { where there is a ", but I don't exactly understand how to create a Map<String, ArrayList<String>> at inviteData from the given response. I don't really get how to define my POJO in order to parse the incoming JSON. Please note that I can't change the format of the JSON, I have to adhere to this format only. Any help would be really appreciated.

Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
Kiran
  • 33
  • 5
  • Possible duplicates [“Expected BEGIN_OBJECT but was STRING at line 1 column 1”](http://stackoverflow.com/questions/28418662/expected-begin-object-but-was-string-at-line-1-column-1) – Fady Saad May 12 '17 at 05:30
  • 1
    @user7790438 No, it is not a duplicate of that. Our poster here knows what the error message means, but doesn't know how to get GSON to do what he wants. Very different question. – ajb May 12 '17 at 05:37

2 Answers2

1

For whatever reason the response you're dealing with was designed, Gson can make it work: Gson supports @JsonAdapter that allows to use ad-hoc (de)serialization.

You seem to use Lombok, but as long as I don't use Lombok and Gson uses fields by default during (de)serialization, you can annotate the inviteData field:

final class LoginResponse {

    final List<InvitationResponse> invitations = null;

}
final class InvitationResponse {

    @JsonAdapter(FromString.class)
    final Map<String, List<String>> inviteData = null;

}

The FromString type adapter factory is very straight-forward:

final class FromString
        implements TypeAdapterFactory {

    // Gson can instantiate it itself   
    private FromString() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Obtaining the original type adapter for the declared field
        return new FromStringTypeAdapter<>(gson.getAdapter(typeToken)).nullSafe();
    }

    private static final class FromStringTypeAdapter<T>
            extends TypeAdapter<T> {

        private final TypeAdapter<T> typeAdapter;

        private FromStringTypeAdapter(final TypeAdapter<T> typeAdapter) {
            this.typeAdapter = typeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final T value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            // Just read the current value as a string, and delegate the job to the obtained type adapter
            final String innerJson = in.nextString();
            return typeAdapter.fromJson(innerJson);
        }

    }

}

That's all. Example:

try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43929916.class, "loginResponse.json") ) {
    final LoginResponse loginResponse = gson.fromJson(jsonReader, LoginResponse.class);
    for ( final InvitationResponse invitation : loginResponse.invitations ) {
        System.out.println(invitation.inviteData);
    }
}

Output:

{821e63e7-457e-401f-b071-abacd51bcbad=[B690E26C-BA39-45E3-BB34-CF6EABB666F7]}

Note that this solution is efficient enough for not large inner JSON since Gson currently only allows to read whole strings from JSON token inputs (thus requiring in-memory bufferring where even such bufferization may consume too much). In order to process bigger inner JSONs, you might want to vote for https://github.com/google/gson/issues/971 that would allow to return string readers thus not requiring intermediate buffers.

Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
0

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at path $.invitations[0].inviteData

So this error states that you are expecting inviteData to be of Type Map<String,ArrayList<String>>, but what is getting returned is not a Map it's String

"inviteData":"{\"821e63e7-457e-401f-b071-abacd51bcbad\":[\"B690E26C-BA39-45E3-BB34-CF6EABB666F7\"]}"
            ||
            ||
            ||
            --->This is a String not of type Map<String,ArrayList<String>>

So You have to make appropriate changes in your POJO,

class InvitationResponse {

    String id;
    String createdAt;
    String updatedAt;
    String version;
    boolean deleted;
    String inviterId;
    String inviterEmail;
    String inviteeId;
    String inviteeEmail;
    String inviteData; ----> This should be String
}

If your JSON would be something like below:

"inviteData" : {
                "someKey" : [ "SomeValue1","SomeValue2" ]
             }

Then your solution would have worked.

Now since this String will be of no use for you, So either you have to change the JSON response if you are the provider of that JSON else you have to make extra efforts change this String value again into Map<String,ArrayList<String>>

Neeraj Jain
  • 7,643
  • 6
  • 34
  • 62