8

I'm new to gson, and have newby question which I have not found an answer to, so please bear with me. StackOverflow and google were not my friend :(

I have a java class "User", and one of its properties, "externalProfile" is a Java String containing already serialized JSON. When gson serializes the User object, it will treat externalProfile as primitive and thus escaping the JSON adding extra slashes etc. I want gson to leave the string alone, just using it "as is", because it is already valid and usable JSON.

To distinguish the JSON string, I created a simple class called JSONString, and I've tried using reader/writers, registerTypeAdapter, but nothing works. Can you help me out?

public class User {
    private JSONString externalProfile;
    public void setExternalProfile(JSONString externalProfile) { this.externalProfile = externalProfile; }

}

public final class JSONString {
    private String simpleString;
    public JSONString(String simpleString) { this.simpleString = simpleString; }
}

public customJsonBuilder(Object object) {
    GsonBuilder builder = new GsonBuilder();
        builder.registerTypeAdapter(GregorianCalendar.class, new JsonSerializer<GregorianCalendar>() {
            public JsonElement serialize(GregorianCalendar src, Type type, JsonSerializationContext context) {
                if (src == null) {
                    return null;
                }
                return new JsonPrimitive(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(src.getTime()));
            }
        });
        Gson gson = builder.create();
        return gson.toJson(object);
}

As en example, the externalProfile will hold (as String value):

{"profile":{"registrationNumber": 11111}}

After I store it as JSONString in the User object, and we convert the user object to JSON:

User user = new User();
user.setExternalProfile(new JSONString(externalProfile)),  
String json = customJsonBuilder(user);

json will hold something like:

{\"profile\":{\"registrationNumber\": 11111}}

So, the externalProfile JSONString is serialized by gson as String primitive, adding the extra slashes in front of the doublequotes. I want gson to leave this JSONString as is, because it already is usable JSON. I'm looking for a type adapter / reader-writer to do this, but I can't get it to work.

TheXL
  • 151
  • 1
  • 8
  • Could it be possible to have an example of input, so that we can reproduce and try to fix your problem? – Alexis C. Jun 13 '15 at 19:17
  • Hi @Alexis C., I've added it to my initial question. Thanks! – TheXL Jun 14 '15 at 13:16
  • Did you check out https://sites.google.com/site/gson/gson-user-guide#TOC-Excluding-Fields-From-Serialization-and-Deserialization ? – uylmz Jun 14 '15 at 13:24
  • Yes I did @Reek, but I DO want the externalProfile in the response! So it should be part of the serialized output of the object, but I want gson to leave it alone (and prevent double serialization). – TheXL Jun 14 '15 at 14:20
  • Hmm, a dirty solution maybe to exclude that property first and add it manually to serialized response. Or, de-serialize externalProfile first, and then serialize User? – uylmz Jun 14 '15 at 14:42
  • Sorry, bit the dirty solution is not an option imho. First of all the User object is part of a more complex object and somewhere in the middle of the automated serialization process. I should hack somewhere in which I don't like doing in general. Furthermore, I try to understand more about gson, and want to know for future purposes also. Also, looking at the nice options gson provides for custom serialization, it should be possible and maybe even quite simple to solve my problem by using gson's API. – TheXL Jun 14 '15 at 16:11
  • Deserializing is a problem too, because the (inner)classes of the externalProfile are not available for me and gson to use – TheXL Jun 14 '15 at 16:17
  • @TheXL Can't you store the `externalProfile` as a `JsonObject` instead? `user.setExternalProfile(new Gson().fromJson(externalProfile, JsonObject.class));` – Alexis C. Jun 14 '15 at 19:07
  • Yes @AlexisC. !! That solves the problem! I can convert it to a JsonObject, and let gson serialize the object again. I checked the deserialize-serialize operations, and it produces exactly the same JSON string. Works like a charm, thanks!! – TheXL Jun 15 '15 at 07:03
  • @Reek, maybe I misunderstood your option about deserializing, but your hint was actually correct, by deserializing to the JsonObject class – TheXL Jun 15 '15 at 07:10

3 Answers3

7

As stated by Alexis C:

store the externalProfile as a JsonObject first:

new Gson().fromJson(externalProfile, JsonObject.class));

And let gson serialize this again when outputting the User object. Will produce exactly the same JSON!

TheXL
  • 151
  • 1
  • 8
6

I solved it without the unnecessary deserialisation-serialization. Create class:

public class RawJsonGsonAdapter extends TypeAdapter<String> {

    @Override
    public void write(final JsonWriter out, final String value) throws IOException {
        out.jsonValue(value);
    }

    @Override
    public String read(final JsonReader in) throws IOException {
        return null; // Not supported
    }
}

And use it by annotation where needed. For example:

public class MyPojo {
    @JsonAdapter(RawJsonGsonAdapter.class)
    public String someJsonInAString;

    public String normalString;
}

That's it. Use Gson as normal.

Daniel
  • 61
  • 1
  • 1
0

Add the read method.

public class RawJsonGsonAdapter extends TypeAdapter<String> {

    @Override
    public void write(final JsonWriter out, final String value) throws IOException {
        out.jsonValue(value);
    }

    @Override
    public String read(final JsonReader in) throws IOException {
        var sb = new StringBuilder();
        int n = 0;
        while (true) {    
            switch (in.peek()) {
            case BEGIN_ARRAY:
                in.beginArray();
                sb.append("[");
                break;
            case BEGIN_OBJECT:
                in.beginObject();
                sb.append("{");
                n++;
                break;
            case BOOLEAN:
                sb.append(in.nextBoolean()).append(",");
                break;
            case END_ARRAY:
                dropLastComma(sb);
                in.endArray();
                sb.append("]");
                break;
            case END_DOCUMENT:
                throw new RuntimeException("END_DOCUMENT invalid here");
            case END_OBJECT:
                dropLastComma(sb);
                in.endObject();
                sb.append("}");
                if (--n == 0)
                    return sb.toString();
                break;
            case NAME:
                sb.append("\"").append(in.nextName()).append("\":");
                break;
            case NULL:
                in.nextNull();
                sb.append("");
                break;
            case NUMBER:
                try {
                    sb.append(in.nextInt()).append(",");
                    break;
                } catch (Exception e1) {
                    try {
                        sb.append(in.nextLong()).append(",");
                        break;
                    } catch (Exception e2) {
                        sb.append(in.nextDouble()).append(",");
                        break;
                    }
                }
            case STRING:
                sb.append("\"").append(in.nextString()).append("\",");
                break;
            }
        }
    }

    private void dropLastComma(StringBuilder sb) {
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.setLength(sb.length() - 1);
        }
    }
}