I have a json like this:
{code:1, message:"ok", data:"W3tpZDoxLCBuYW1lOiJUb20ifSx7aWQ6MiwgbmFtZToiSmFjayJ9LHtpZDozLCBuYW1lOiJMdWNpYSJ9XQ=="}
, only data is encoded using base64, the real data is:
[{id:1, name:"Tom"},{id:2, name:"Jack"},{id:3, name:"Lucia"}]
. How to use gson to deserialize this json in one step?
Gson suggests a couple of options here, but the following seems to be the shortest one. Consider the following Response and User classes:
final class Response<T> {
@SerializedName("code")
final int code = Integer.valueOf(0);
@SerializedName("message")
final String message = null;
@SerializedName("data")
@JsonAdapter(Base64TypeAdapterFactory.class)
final T data = null;
}
final class User {
final int id = Integer.valueOf(0);
final String name = null;
@Override
public String toString() {
return id + ":" + name;
}
}
@JsonAdapter(Base64TypeAdapterFactory.class) above tells Gson to pick a special type adapter for the given field.
This type adapter will be responsible for Base64 decoding and the actual type can be specialized using type tokens during deserialization.
final class Base64TypeAdapterFactory
implements TypeAdapterFactory {
// Gson can instantiate this one itself, no need to expose it
private Base64TypeAdapterFactory() {
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final TypeAdapter<String> stringTypeAdapter = gson.getAdapter(String.class);
final TypeAdapter<T> dataTypeAdapter = gson.getAdapter(typeToken);
return Base64TypeAdapter.of(stringTypeAdapter, dataTypeAdapter);
}
private static final class Base64TypeAdapter<T>
extends TypeAdapter<T> {
private static final Decoder base64Decoder = Base64.getDecoder();
private final TypeAdapter<String> stringTypeAdapter;
private final TypeAdapter<T> dataTypeAdapter;
private Base64TypeAdapter(final TypeAdapter<String> stringTypeAdapter, final TypeAdapter<T> dataTypeAdapter) {
this.stringTypeAdapter = stringTypeAdapter;
this.dataTypeAdapter = dataTypeAdapter;
}
static <T> TypeAdapter<T> of(final TypeAdapter<String> stringTypeAdapter, final TypeAdapter<T> dataTypeAdapter) {
return new Base64TypeAdapter<>(stringTypeAdapter, dataTypeAdapter)
.nullSafe(); // Just let Gson manage nulls itself. It's convenient
}
@Override
public void write(final JsonWriter jsonWriter, final T value) {
throw new UnsupportedOperationException();
}
@Override
public T read(final JsonReader jsonReader)
throws IOException {
// Decode the payload first as a Base64-encoded message
final byte[] payload = base64Decoder.decode(stringTypeAdapter.read(jsonReader));
try ( final JsonReader payloadJsonReader = new JsonReader(new InputStreamReader(new ByteArrayInputStream(payload))) ) {
// And tell Gson to not refuse unquoted property names
payloadJsonReader.setLenient(true);
return dataTypeAdapter.read(payloadJsonReader);
} catch ( final EOFException ignored ) {
return null;
}
}
}
}
Now you can easily test it:
final Response<List<User>> response = gson.fromJson(..., new TypeToken<Response<List<User>>>() {
}.getType());
System.out.println(response.code);
System.out.println(response.message);
System.out.println(response.data);
The output:
1
ok
[1:Tom, 2:Jack, 3:Lucia]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With