[Java] Vert.x Jackson date data is displayed normally after serialization

There hasn’t been an update for a while, as everyone knows it’s the end of the year.

In fact, I recently had an idea to open source my vtx_fw framework. But there is still a lot of finishing work that needs to be done before open source (I can’t let you laugh at this o(╥﹏╥)o). I discovered a problem today, and I will summarize it and share it with you immediately.

This problem is the display of date type data in the Vert.x framework under Jackson serialization. Friends who have played Vert.x know that the vertx-core package relies on the jackson-core package by default. If the entity class only contains numeric and string fields, it can be serialized directly using Json.encode. However, once you encounter date types, such as LocalDateTime and Date, the following errors will appear, as shown below:

io.vertx.core.json.EncodeException: Mapping io.kida.yuen.utils.system.router.RouterValue  is not available without Jackson Databind on the classpath
	at io.vertx.core.json.jackson.JacksonCodec.encodeJson(JacksonCodec.java:329)
	at io.vertx.core.json.jackson.JacksonCodec.toString(JacksonCodec.java:95)
	at io.vertx.core.spi.json.JsonCodec.toString(JsonCodec.java:47)
	at io.vertx.core.json.Json.encode(Json.java:49)
	at io.kida.yuen.dao.crud.SelectDaoMapper.lambda$2(SelectDaoMapper.java:80)
 ...

As shown in the figure above, the error was located in the Json.encode method in my class named SelectDaoMapper. After investigation, it was found that the Json.encode method cannot map entities. It suggested that I introduce jackson-databind dependency, as shown below:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>

After adding the jackson-databind dependency, re-execute the method to get new error information, as shown below:

io.vertx.core.json.EncodeException: Failed to encode as JSON: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: io.kida.yuen.utils.system.router.RouterValue["retData"]->io.kida.yuen.vo.datasource.DynamicRetData["rows"]->java.util.ArrayList[0]->java.util.TreeMap["accessDate"])
	at io.vertx.core.json.jackson.DatabindCodec.toString(DatabindCodec.java:163)
	at io.vertx.core.spi.json.JsonCodec.toString(JsonCodec.java:47)
	at io.vertx.core.json.Json.encode(Json.java:49)
	at io.kida.yuen.dao.crud.SelectDaoMapper.lambda$2(SelectDaoMapper.java:80)
...

According to the error message, Jackson does not support the date type of Java 8. If you want to process the Java date type, you need to introduce the jackson-datatype-jsr310 dependency. (⊙o⊙)…Okay, as shown below:

<!-- jackson -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

After adding the dependency, I re-executed the method and found that the error still exists (doesn’t it mean that adding the jackson-datatype-jsr310 dependency is enough?), so I checked online and found that after adding the jackson-datatype-jsr310 dependency, I still need to enable the date module to function properly. parse. As shown below:

ObjectMapper mapper = DatabindCodec.mapper();
mapper.registerModule(new JavaTimeModule());

So create a class named JacksonConfig and let it inherit AbstractVerticle, then rewrite the start method and add the code to it (when the system deploys JacksonConfig, it will automatically execute the code in the start method to achieve automatic loading), as shown below:

public class JacksonConfig extends AbstractVerticle {
    
    

    @Override
    public void start() {
    
    
        ObjectMapper mapper = DatabindCodec.mapper();
        mapper.registerModule(new JavaTimeModule());
    }
}

I thought that calling the method this time would definitely get the result I wanted, but the result... Although it was returned, the format returned by the date field was not the format we "wanted", as shown below:

{
    
    
  "retCode": 1,
  "retMsg": "Search Complete",
  "retData": {
    
    
    "rows": [
      {
    
    
        "accessDate": [
          2023,
          10,
          17,
          17,
          23,
          21
        ],
        "buzzId": 1,
        "id": 1,
        "ip": "127.0.0.1",
        "operType": "insert"
      }
    ],
    "numRows": 1
  }
}

The accessDate field is a LocalDateTime type in Java entities, as shown below:

...

@Column(name = "ACCESS_DATE")
private LocalDateTime accessDate;

...

Later I learned that Jackson will automatically serialize date data into an array by default. So how to solve it? There are basically three solutions that can be found online:

  1. @JsonFormat annotation to specify the format;
  2. Rewrite JsonSerializer to customize serialization logic;
  3. Register a custom serializer in ObjectMapper;

But since I encapsulated the query method into a tool this time, these three methods are not the optimal solution in my mind. In the end, I solved this problem through "reconversion", as shown below:

public static void query(DataSourceExecParam dbem, Handler<DynamicRetData> resultHandler) {
    
    
        if (!DataSourceConstants.JDBC_CLIENT_MAP.isEmpty()) {
    
    
            DataSourceClientUtil.dbClient(dbem).getConnection(ar -> {
    
    
                if (ar.succeeded()) {
    
    
                    SQLConnection connection = ar.result();
                    connection.query(dbem.getExecSql(), reHandler -> {
    
    
                        if (reHandler.succeeded()) {
    
    
                            ResultSet rs = reHandler.result();
                            DynamicRetData add = new DynamicRetData();

                            // 将返回的resultset分解并存入AjaxDyncData中方便后续使用
                            if (null != rs) {
    
    
                                // 返回的行数有多少条
                                add.setNumRows(rs.getNumRows());
                                // 获取返回数据集,格式是jsonobject能够与columnName字段做映射 这里设定返回字段大小写不敏感,这样方便统一处理
                                List<JsonObject> list = rs.getRows(true);


                                // ----- 开始
                                list.stream().forEach(jsonObj -> {
    
    
                                    jsonObj.stream().filter(entry -> entry.getValue() instanceof LocalDateTime
                                        || entry.getValue() instanceof Date).forEach(entry -> {
    
    
                                            LocalDateTime ldt = LocalDateTime.parse(jsonObj.getString(entry.getKey()));
                                            jsonObj.put(entry.getKey(),
                                                ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).replace("T", " "));
                                        });
                                });
								// ----- 结束

                                
                                add.setRows(list);
                            }
                            resultHandler.handle(add);
                        } else {
    
    
                            log.error(QUERY_EXCEPTION_OUTPUT + reHandler.cause());
                            resultHandler.handle(null);
                        }
                    }).close();
                } else {
    
    
                    log.error(CREATE_CLIENT_EXCEPTION_OUTPUT + ar.cause());
                    resultHandler.handle(null);
                }
            });
        } else {
    
    
            log.error(CREATE_NO_PREPARE_OUTPUT);
            resultHandler.handle(null);
        }
    }

The above is my query method. The code enclosed in the "//----- start" and "//----- end" comments is the "re-conversion" code. When the data is in rs, which is ResultSet, the time field is stored through TreeMap. At this time, it is still a "normal" "date type". It will be automatically converted through Jackson after the getRows method is called, so what is obtained in the list variable is the converted array of dates.

What we need to do at this time is to convert the converted "date" back again. At this time we will use the LocalDateTime.parse method. When calling LocalDateTime.parse, you will be asked to pass in the converted time array, and then call the format method to convert it according to the DateTimeFormatter.ISO_LOCAL_DATE_TIME format. The final date data will have a "T" added between "date" and "time". keyword, but we don’t need this “T”, so we can replace it with nothing using the replace method. This will generate "normal" date output, as shown below:

{
    
    
  "retCode": 1,
  "retMsg": "Search Complete",
  "retData": {
    
    
    "rows": [
      {
    
    
        "accessDate": "2023-10-17 17:23:21",
        "buzzId": 1,
        "id": 1,
        "ip": "127.0.0.1",
        "operType": "insert"
      }
    ],
    "numRows": 1
  }
}

Guess you like

Origin blog.csdn.net/kida_yuan/article/details/133934419