如何为Gson编写自定义JSON反序列化器?

57

我有一个Java类,叫做User:

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

我从一个 Web 服务接收到一个包含用户对象的 JSON 列表:

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

我尝试编写了一个自定义的反序列化程序:

@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));
}

但是我的反序列化器不起作用。我该如何编写Gson的自定义JSON反序列化器?


这个例子有帮助吗?https://dev59.com/x2025IYBdhLWcg3wtoQ_ - Matt Ball
你说的“不工作”是什么意思?出了什么问题? - ColinD
1
“date_date” 应该改为 “update_date” 吗? - Programmer Bruce
3个回答

93

我会采用稍微不同的方法,以尽量减少代码中的“手动”解析,否则就会有点违背我使用像Gson这样的API的初衷。

// 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);
  }
}

(这里假设原问题中的"date_date"应该是"update_date")


3
我更喜欢这个模型——如果时间戳是唯一无法解析的东西,最好让时间戳可序列化,而不是手动解析整个用户对象。毕竟 Gson 已经完全能够做到这一点。 - dimo414
小修改。我们可以使用“return new Timestamp(json.getAsLong());”代替“long time = Long.parseLong(json.getAsString()); return new Timestamp(time);”。 - Nishanth
您还可以使用方法链来构建GsonBuilder,从而提高可读性。 - MC Emperor
在反序列化器中,有没有一种方法可以知道当前正在处理的字段名称? - Alanight
非常好的答案...它完美地解决了我的问题....非常感谢 :) - Murli

58
@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()));
}

我假设User类有适当的构造函数。


1
这种方法违背了使用GSON的初衷。 - SMR
我同意,它应该改为return context.deserialize(json, User.class); - nilesh

1

今天我在寻找一个东西,因为我的课程中有 java.time.Instant,而默认的gson无法反序列化它。我的POJOs看起来像这样:

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
)

然后对于Instant变量,我解析JSON的时间变量并将字符串转换为Instant。对于整数、字符串等,我使用jsonObject.get("id").asInt等方法。对于其他POJO,我使用默认的反序列化程序,如下所示:

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

因此,相应的自定义反序列化程序如下所示:
  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;
    }

  }

最终,我像这样创建了自定义的Gson:

 val gsonBuilder = GsonBuilder();
 gsonBuilder.registerTypeAdapter(RewardResult::class.javaObjectType,
          this.customJsonDeserializer);
      val customGson: Gson = gsonBuilder.create();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接