Converting JSON between string and byte[] with GSON

You can use this adapter to serialize and deserialize byte arrays in base64. Here's the content.

   public static final Gson customGson = new GsonBuilder().registerTypeHierarchyAdapter(byte[].class,
            new ByteArrayToBase64TypeAdapter()).create();

    // Using Android's base64 libraries. This can be replaced with any base64 library.
    private static class ByteArrayToBase64TypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
        public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            return Base64.decode(json.getAsString(), Base64.NO_WRAP);
        }

        public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(Base64.encodeToString(src, Base64.NO_WRAP));
        }
    }

Credit to the author Ori Peleg.


This is works for me:

Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss")
        .registerTypeAdapter(byte[].class, (JsonSerializer<byte[]>) (src, typeOfSrc, context) -> new JsonPrimitive(new String(src)))
        .registerTypeAdapter(byte[].class, (JsonDeserializer<byte[]>) (json, typeOfT, context) -> json == null ? null : json.getAsString() == null ? null : json.getAsString().getBytes())
        .create();

From some blog for future reference, incase the link is not available, atleast users can refer here.

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

import java.lang.reflect.Type;
import java.util.Date;

public class GsonHelper {
    public static final Gson customGson = new GsonBuilder()
            .registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
                @Override
                public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                return new Date(json.getAsLong());
                }
            })
            .registerTypeHierarchyAdapter(byte[].class,
                    new ByteArrayToBase64TypeAdapter()).create();

    // Using Android's base64 libraries. This can be replaced with any base64 library.
    private static class ByteArrayToBase64TypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
        public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            return Base64.decode(json.getAsString(), Base64.NO_WRAP);
        }

        public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(Base64.encodeToString(src, Base64.NO_WRAP));
        }
    }
}

I needed the solution to comply with the OpenAPI specification that states String type of format byte has to hold base64 encoded values. POJO holds byte[] and JSON object has String property.

In my case I couldn't use android library for base64 processing as Gson serialization was used in backend application. For this reason I used java provided java.util.Base64 class.

Instead of adapter based on JsonSerializer<byte[]>, JsonDeserializer<byte[]> interfaces I used TypeAdapter<byte[]> adapter. With it I could override HTML escaping functionality of Gson to prevent escaping padding character '=' in base64 encoding. If this isn't done then encoded value of "hello World"would be "aGVsbG8gV29ybGQ\u003d" instead of "aGVsbG8gV29ybGQ=" and deserializer on other side would fail.

Below is the code of custom adapter:

public class ByteArrayAdapter extends TypeAdapter<byte[]> {

  @Override
  public void write(JsonWriter out, byte[] byteValue) throws IOException {
    boolean oldHtmlSafe = out.isHtmlSafe();
    try {
      out.setHtmlSafe(false);
      out.value(new String(Base64.getEncoder().encode(byteValue)));
    } finally {
      out.setHtmlSafe(oldHtmlSafe);
    }
  }

  @Override
  public byte[] read(JsonReader in) {
    try {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return new byte[]{};
      }
      String byteValue = in.nextString();
      if (byteValue != null) {
        return Base64.getDecoder().decode(byteValue);
      }
      return new byte[]{};
    } catch (Exception e) {
      throw new JsonParseException(e);
    }
  }
}