Eliezer
Eliezer

Reputation: 7347

GSON Integer to Boolean for Specific Fields

I'm dealing with an API that sends back integers (1=true, other=false) to represent booleans.

I've seen this question and answer, but I need to be able to specify which field this should apply to, since some times an integer is actually an integer.

EDIT: The incoming JSON could possibly look like this (could also be String instead of int, etc...):

{ 
    "regular_int": 1234, 
    "int_that_should_be_a_boolean": 1
}

I need a way to specify that int_that_should_be_a_boolean should be parsed as a boolean and regular_int should be parsed as an integer.

Upvotes: 17

Views: 14693

Answers (2)

Maheshwar Ligade
Maheshwar Ligade

Reputation: 6855

We will provide Gson with a little hook, a custom deserializer for booleans, i.e. a class that implements the JsonDeserializer<Boolean> interface:

CustomBooleanTypeAdapter

import java.lang.reflect.Type;
import com.google.gson.*;
class BooleanTypeAdapter implements JsonDeserializer<Boolean> {
public Boolean deserialize(JsonElement json, Type typeOfT,
                           JsonDeserializationContext context) throws JsonParseException {
    if (((JsonPrimitive) json).isBoolean()) {
        return json.getAsBoolean();
    }
    if (((JsonPrimitive) json).isString()) {
        String jsonValue = json.getAsString();
        if (jsonValue.equalsIgnoreCase("true")) {
            return true;
        } else if (jsonValue.equalsIgnoreCase("false")) {
            return false;
        } else {
            return null;
        }
    }

    int code = json.getAsInt();
    return code == 0 ? false :
            code == 1 ? true : null;
  }
}

To use it we’ll need to change a little the way we get the Gson mapper instance, using a factory object, the GsonBuilder, a common pattern way to use GSON is here.

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Boolean.class, new BooleanTypeAdapter());
Gson gson = builder.create();

For primitive Type use below one

 GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapter(boolean.class, new BooleanTypeAdapter());
    Gson gson = builder.create();

Enjoy the JSON parsing!

Upvotes: 37

user1531971
user1531971

Reputation:

If I'm understanding correctly, you want to normalize or massage the value on the incoming JsonReader from an int to something else, like a boolean.

If so, you probably want to create an adapter class that extends TypeAdapter<YourFieldNameType> and overrides read(). You get the value from nextInt() and then return the appropriate boolean based on its value. You may have to check for null values, depending on the config of the parser.

If you need to, you can override write() in the same adapter class, to coerce booleans in your client code into integers for the JsonWriter.

[EDIT]

For reference, here is an example of my TypeAdapter, which coerces a "Commands" enum class to/from an integer.

package com.company.product.json;

import static com.company.product.Commands.*;

import java.io.IOException;
import java.util.logging.Logger;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.company.product.Commands;
import com.company.product.client.ClientSocket;

/**
 * Adapter for Command handling.
 * 
 * We write out the CommandName as an Integer, and read it in as a Commands constant.
 * 
 * This satisfies the requirement that the CommandName by represented by JSON as an int, but allows
 * us to deserialize it to a Commands object on read.
 * 
 * @author jdv
 * @see com.company.product.Command#commandName CommandName
 */
public class CommandsAdapter extends TypeAdapter<Commands> {

  private static final Logger logger = Logger.getLogger(ClientSocket.class.getPackage().getName());

  /*
   * (non-Javadoc) Deserialize the JSON "CommandName" integer into the corresponding Commands
   * constant object.
   * 
   * @see com.google.gson.TypeAdapter#read(com.google.gson.stream.JsonReader)
   */
  @Override
  public Commands read(JsonReader in) throws IOException {

    final int command;
    try {
      command = in.nextInt();

    } catch (IllegalStateException e) {
      logger.severe("Unable to read incoming JSON stream: " + e.getMessage());
      throw new IOException(e);

    } catch (NumberFormatException e) {
      logger
          .severe("Unable to read and convert CommandName Integer from the incoming JSON stream: "
              + e.getMessage());
      throw new IOException(e);
    }

    // Let's not risk using a bad array index. Not every command is expected
    // by the WebSocket handlers, but we should do our best to construct
    // a valid Commands object.
    if (command < NO_OP.getValue() || command > LAST_COMMAND.getValue()) {
      throw new IOException(new IllegalArgumentException(
          "Unexpected value encountered for Commands constant: " + command));
    } else {
      return Commands.values()[command];
    }
  }

  /*
   * (non-Javadoc) Serialize Commands object constants as their Integer values.
   * 
   * @see com.google.gson.TypeAdapter#write(com.google.gson.stream.JsonWriter, java.lang.Object)
   */
  @Override
  public void write(JsonWriter out, Commands value) throws IOException {
    out.value(value.getValue());
  }

}

This essentially adapts incoming and outgoing ops on the "CommandName" serialized param that is locally represented as a "Commands" enum and as an integer remotely. Anything to do with this Commands enum is filtered through this adapter class that overrides read() and write(). Eventually this drives a WebSocket peer, but none of that really matters for the purpose of this discussion. HTH.

Upvotes: 1

Related Questions