57

I have a Java class, User:

public class User
{
    int id;
    String name;
    Timestamp updateDate;
}

And I receive a JSON list containing user objects from a webservice:

[{"id":1,"name":"Jonas","update_date":"1300962900226"},
{"id":5,"name":"Test","date_date":"1304782298024"}]

I have tried to write a custom deserializer:

@Override
public User deserialize(JsonElement json, Type type,
                        JsonDeserializationContext context) throws JsonParseException {

        return new User(
            json.getAsJsonPrimitive().getAsInt(),
            json.getAsString(),
            json.getAsInt(),
            (Timestamp)context.deserialize(json.getAsJsonPrimitive(),
            Timestamp.class));
}

But my deserializer doesn't work. How can I write a custom JSON deserializer for Gson?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jonas
  • 121,568
  • 97
  • 310
  • 388

3 Answers3

93

I'd take a slightly different approach as follows, so as to minimize "manual" parsing in my code, as unnecessarily doing otherwise somewhat defeats the purpose of why I'd use an API like Gson in the first place.

// output:
// [User: id=1, name=Jonas, updateDate=2011-03-24 03:35:00.226]
// [User: id=5, name=Test, updateDate=2011-05-07 08:31:38.024]

// using java.sql.Timestamp

public class Foo
{
  static String jsonInput = 
    "[" +
      "{\"id\":1,\"name\":\"Jonas\",\"update_date\":\"1300962900226\"}," +
      "{\"id\":5,\"name\":\"Test\",\"update_date\":\"1304782298024\"}" +
    "]";

  public static void main(String[] args)
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    gsonBuilder.registerTypeAdapter(Timestamp.class, new TimestampDeserializer());
    Gson gson = gsonBuilder.create();
    User[] users = gson.fromJson(jsonInput, User[].class);
    for (User user : users)
    {
      System.out.println(user);
    }
  }
}

class User
{
  int id;
  String name;
  Timestamp updateDate;

  @Override
  public String toString()
  {
    return String.format(
      "[User: id=%1$d, name=%2$s, updateDate=%3$s]",
      id, name, updateDate);
  }
}

class TimestampDeserializer implements JsonDeserializer<Timestamp>
{
  @Override
  public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  {
    long time = Long.parseLong(json.getAsString());
    return new Timestamp(time);
  }
}

(This assumes that "date_date" should be "update_date", in the original question.)

Programmer Bruce
  • 64,977
  • 7
  • 99
  • 97
  • 3
    I definitely prefer this model - if Timestamp is the only thing failing to parse, better to make Timestamp deserializable, rather than manually parse the whole User object, when Gson is already perfectly capable of doing so. – dimo414 Oct 19 '12 at 13:14
  • small edit. we can use "return new Timestamp(json.getAsLong());" instead of "long time = Long.parseLong(json.getAsString()); return new Timestamp(time);" – Nishanth Jan 07 '16 at 20:53
  • You can also method-chain the `GsonBuilder`, improving readability. – MC Emperor Sep 11 '17 at 12:43
  • Is there any method to know the ***field name*** that is processing currently in ***Deserializer***? – Alanight May 22 '18 at 06:53
  • excellent answer...it solved my problem perfectly....thanks alot :) – Murli Nov 15 '19 at 13:54
57
@Override
public User deserialize(JsonElement json, Type type,
        JsonDeserializationContext context) throws JsonParseException {

    JsonObject jobject = json.getAsJsonObject();

    return new User(
            jobject.get("id").getAsInt(), 
            jobject.get("name").getAsString(), 
            new Timestamp(jobject.get("update_date").getAsLong()));
}

I'm assuming User class has the appropriate constructor.

randers
  • 5,031
  • 5
  • 37
  • 64
Peter Knego
  • 79,991
  • 11
  • 123
  • 154
1

Today I was looking for this thing as my class had java.time.Instant and the default gson could not deserialize it. My POJOs look like this:

open class RewardResult(
  @SerializedName("id")
  var id: Int,
  @SerializedName("title")
  var title: String?,
  @SerializedName("details")
  var details: String?,
  @SerializedName("image")
  var image: String?,
  @SerializedName("start_time")
  var startTimeUtcZulu: Instant?,   // Unit: Utc / Zulu. Unit is very important
  @SerializedName("end_time")
  var endTimeUtcZulu: Instant?,
  @SerializedName("unlock_expiry")
  var unlockExpiryTimeUtcZulu: Instant?,
  @SerializedName("target")
  var target: Int,
  @SerializedName("reward")
  var rewardItem: RewardItem
);

data class RewardItem(
  @SerializedName("type")
  var type: String?,
  @SerializedName("item_id")
  var itemId: Int,
  @SerializedName("amount")
  var amount: Int
)

Then for Instant variables, I parse the json's time variables and convert string to Instant. For integer , string, etc I use jsonObject.get("id").asInt etc. For other pojo, I use the default deserializer like this:

val rewardItem: RewardItem = context!!.deserialize(rewardJsonElement,
        RewardItem::class.java);

So the corresponding custom deserializer looks like this:

  val customDeserializer: JsonDeserializer<RewardResult> = object : JsonDeserializer<RewardResult> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): RewardResult {
      val jsonObject: JsonObject = json!!.asJsonObject;

      val startTimeString: String? = jsonObject.get("start_time")?.asString;
      var startTimeUtcZulu: Instant? = createTimeInstant(startTimeString);


      val endTimeString: String? = jsonObject.get("end_time")?.asString;
      var endTimeUtcZulu: Instant? = createTimeInstant(endTimeString);

      val unlockExpiryStr: String? = jsonObject.get("unlock_expiry")?.asString;
      var unlockExpiryUtcZulu: Instant? = createTimeInstant(unlockExpiryStr);

      val rewardJsonElement: JsonElement = jsonObject.get("reward");
      val rewardItem: ridmik.one.modal.reward.RewardItem = context!!.deserialize(rewardJsonElement,
          ridmik.one.modal.reward.RewardItem::class.java);  // I suppose this line means use the default jsonDeserializer

      var output: ridmik.one.modal.reward.RewardResult = ridmik.one.modal.reward.RewardResult(
          id = jsonObject.get("id").asInt,
          title = jsonObject.get("title")?.asString,
          details = jsonObject.get("details")?.asString,
          image = jsonObject.get("image")?.asString,
          startTimeUtcZulu = startTimeUtcZulu,
          endTimeUtcZulu = endTimeUtcZulu,
          unlockExpiryTimeUtcZulu = unlockExpiryUtcZulu,
          target = jsonObject.get("target").asInt,
          rewardItem = rewardItem
      );

      Timber.tag(TAG).e("output = "+output);

      return output;
    }

  }

Finally, I create my custom gson like this:

 val gsonBuilder = GsonBuilder();
 gsonBuilder.registerTypeAdapter(RewardResult::class.javaObjectType,
          this.customJsonDeserializer);
      val customGson: Gson = gsonBuilder.create();
Qazi Fahim Farhan
  • 2,066
  • 1
  • 14
  • 26