GSON parsing unspecified type variable

2020-03-31 04:40发布

问题:

I parse server JSON response with GSON library. Backend guys sometimes tell me: "We can't specify variable type in JSON for some reason" (old php, they don't know how to do it and so on and so forth). GSON likes strong typing in its object model. So I can't parse Object as String.

GSON wait for:

{
    "service":{
        "description":null,
        "name":"Base",
        "id":"4c7a90410529"
    }
}

But it gets (empty data):

"service": ""

And I get

java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1396

What is the best practice to parse such response?

Another question: How can I build object, it can recognize Integer variable which returned from time to time as Integer or as String? The same server side issue.

"data": "1"

or

"data": 1

I know - we should use specific types in Java. But sometime it is worth to make concessions,
Thanks

EDIT: My solution based on Java Developer's answer.
ServiceDeserializer class deserialize every object depending on its internal value.

public class ServiceDeserializer implements JsonDeserializer<ServiceState>{

    @Override
    public ServiceState deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        String name = "";
        String uuid = "";
        String description = "";

        if (json.isJsonObject()) {
            JsonObject obj = json.getAsJsonObject();

            if (!obj.get("name").isJsonNull()) {
                name = obj.get("name").getAsString();
            }
            if (!obj.get("uuid").isJsonNull()) {
                uuid = obj.get("uuid").getAsString();
            }
            if (!obj.get("description").isJsonNull()) {
                description = obj.get("description").getAsString();
            }
        }
        return new ServiceState(name, uuid, description);
    }

}

And my GSON constructor with type adapter for ServiceState.

Gson gson = new GsonBuilder()
    .registerTypeAdapter(ServiceState.class, new ServiceDeserializer())
    .create();

回答1:

If you want to stick with strictly gson you can provide a custom deserializer. Since we know that service is either a property of the base json string or embedded within some other property, we can use the deserializer to step-wise parse out the offending components and handle them accordingly.

 public class MyJsonDeserializer implements JsonDeserializer<YourParsedData> {

   @Override 
   public YourParsedData deserialize(final JsonElement je, final Type type, final JsonDeserialization Context jdc) throws JsonParseException
   {
      final JsonObject obj = je.getAsJsonObject(); //our original full json string
      final JsonElement serviceElement = obj.get("service");


     //here we provide the functionality to handle the naughty element. It seems emtpy string is returned as a JsonPrimitive... so one option
     if(serviceElement instanceOf JsonPrimitive)
     {
       //it was empty do something
     }

     return YourParsedData.create(); //provide the functionality to take in the parsed data
   }
 }

The custom deserializer would be called as follows:

 final Gson gson = new GsonBuilder().registerTypeAdapter(YourParsedData.class, new MyJsonDeserializer()).create();
 gson.fromJson("{service: ''}", YourParsedData.class);

I typed all this up so if I missed some syntax my apologies.



回答2:

You need to scrape the JSON response before trying to deserialize it into your Response Java object. You can make use of Java org.json parser to verify that service object actually exists and fix it otherwise.

String json = "{\"service\":{\r\n" + 
        "    \"description\":null,\r\n" + 
        "    \"name\":\"Base\",\r\n" + 
        "    \"id\":\"4c7a90410529\"\r\n" + 
        "}}";
String json2 = "{\"service\":\"\"}";

JSONObject root = new JSONObject(json);
// JSONObject root = new JSONObject(json2);
if (root.optJSONObject("service") == null) {
    root.put("service", new JSONObject());
}

Gson gson = new Gson();
Response response = gson.fromJson(root.toString(), Response.class);

System.out.println(response.getService());

Output :

// for JSONObject root = new JSONObject(json);
Service [id=4c7a90410529, name=Base, description=null]

// for JSONObject root = new JSONObject(json2);
Service [id=null, name=null, description=null]

Secondly, Gson is smart enough to do simple conversions like String to Integer etc. So, deserializing such JSON properties shouldn't give you any troubles.

System.out.println(gson.fromJson("10", Integer.class)); // 10
System.out.println(gson.fromJson("\"20\"", Integer.class)); // 20


回答3:

Your json is invalid and any Json parser wouldn't be able to parse a syntactically incorrect json:

   "service": {
        "description": null,
        "name": "Base",
        "id": "4c7a90410529"
    }

should be encapsulated in curly braces as mentioned here:

{
    "service": {
        "description": null,
        "name": "Base",
        "id": "4c7a90410529"
    }
}


回答4:

A json structure is enclosed within {}. Your response seems to be missing that. You can manually append { and } at the beginning and end of the string to make it into a valid json structure.

Once this is done, you can use Gson to parse your json response normally.

What is the best practice to parse such response?

Use a good enough Json parser. That's more than enough. And try to have a class representing the exact same Structure as the response to avoid parsing the json responses level by level, manually.