Jackson List to Object

最近有个想法,把一些比较通用的逻辑拆出来放到一个单独的类里面。

举个比较简单的例子就是报表查询的时候,某一查询条件存在多种组合方式和要求,比如数据原本是这样的

{
  "list" : [1,2,3,4,5],
  "name" : "night",
  "user" : 1
}

实际上我们需要调用另外一个方法将其转为

{
  "list" : [1,-2,3,-4,5],
  "name" : "night",
  "user" : 1
}

也就是说要根据业务需要将其中一部分数据进行处理,然后再执行最终的查询

通常这种情况下我们需要直接写代码调用另外一个方法,将数据传进去进行处理,但是这种方式带来的问题就是维护性的下降

如果能有某一个类代表此类的逻辑,前端直接传值到此对象中,他自行进行数据处理,我们只需要直接调用get()方法就能拿到想要的参数就好了

之前在公司的时候简单的尝试了一下,发现list无法直接转为class对象,@JsonDeserialize处理起来很麻烦,于是想着抽象一下,可以直接使用就好了。

经过了半小时的查阅资料,终于解决了这个问题。

首先定义一个基础数据类

@Data
public class MyData<R> {

  private List<R> list;

  public MyData() {
  }

  public MyData(List<R> list) {
    this.list = list;
  }
}

这个类主要用于其他类继承并实现功能

然后就是实现类,这里我直接起名叫部门类,你也可以根据你的业务修改此名称,该类继承上面的mydata类

@Data
@NoArgsConstructor
public class DepartmentIds extends MyData<Long>{

}

接下来就是jackson的序列化类和反序列化类

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class BaseListToObjectDeserializer<T extends MyData,R>{

  public JsonDeserializer<T> get(Class<T> t, Function<String,R> strToData){
    return new JsonDeserializer<T>() {
      @Override
      public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        T result = null;
        try {
          result = t.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
          throw new RuntimeException(e);
        }

        JsonNode node = p.readValueAsTree();
        List<R> collect = IntStream.range(0, node.size()).boxed().map(x -> strToData.apply(node.get(x).toString())).collect(Collectors.toList());
        result.setList(collect);
        return result;
      }
    };
  }
}

序列化类 (由于jackson的writeArray仅支持int,string,long,double四种数组入参,因此就写了四种)

package com.example.springrabbitmq;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

public class BaseListToObjectSerializer<T extends MyData<R>,R> {

  public JsonSerializer<T> getInt(Class<T> t, Function<List<R>, int[]> strToData) {
    return new JsonSerializer<T>() {
      @Override
      public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (Objects.nonNull(value)) {
          int[] apply = strToData.apply(value.getList());
          gen.writeArray(apply, 0, apply.length);
        }
      }
    };
  }

  public JsonSerializer<T> getString(Class<T> t, Function<List<R>, String[]> strToData) {
    return new JsonSerializer<T>() {
      @Override
      public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (Objects.nonNull(value)) {
          String[] apply = strToData.apply(value.getList());
          gen.writeArray(apply, 0, apply.length);
        }
      }
    };
  }

  public JsonSerializer<T> getLong(Class<T> t, Function<List<R>, long[]> strToData) {
    return new JsonSerializer<T>() {
      @Override
      public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (Objects.nonNull(value)) {
          long[] apply = strToData.apply(value.getList());
          gen.writeArray(apply, 0, apply.length);
        }
      }
    };
  }
}

配置spring jackson配置

package com.example.springrabbitmq.config;

import com.example.springrabbitmq.BaseListToObjectDeserializer;
import com.example.springrabbitmq.BaseListToObjectSerializer;
import com.example.springrabbitmq.DepartmentIds;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.math.BigInteger;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebSerializerConfig {

  @Bean
  public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
        .serializerByType(Long.class, ToStringSerializer.instance)
        .serializerByType(BigInteger.class, ToStringSerializer.instance)
        // 这里我们每添加一种mydata实现就需要新增一行类似代码到这里,像这里我的DepartmentIds实际承载的数据是long类型的,因此在getLong中需要传入x -> x.stream().mapToLong(l -> l).toArray())这么一段来转换数据,当然这块还可以再优化一下
        .serializerByType(DepartmentIds.class, new BaseListToObjectSerializer<DepartmentIds,Long>().getLong(DepartmentIds.class, x -> x.stream().mapToLong(l -> l).toArray()))
        // 反序列化,同上,每新增类型就加一行
        .deserializerByType(DepartmentIds.class, new BaseListToObjectDeserializer<DepartmentIds,Long>().get(DepartmentIds.class, Long::valueOf))
        ;
  }
}

调用示例:

@Slf4j
@RestController
@RequestMapping("test")
public class JacksonDemo {

  @PostMapping("1")
  public Param test(@RequestBody Param param){
    log.info("param:{}",param);
    Param result = new Param();
    BeanUtils.copyProperties(param, result);
    return result;
  }
}

@Data
public class Param {
  
  private DepartmentIds list;

  private String name;
}


curl --request POST \
    --url http://127.0.0.1:8080/test/1 \
    --header 'Content-Type: application/json' \
    --data '{
    "list" : [1,2,3,4,5],
    "name" : "李华"
    }'

可以看到返回结果是这样的,list参数的入参和出参不再被对象包裹,直接在root节点

{
	"list": [1, 2, 3, 4, 5],
	"name": "李华"
}

如果没有以上的代码的话,你的请求参数本应该是这样的,返回参数也是如此,并且你需要手动调用方法来处理list中的数据,现在我们只需要在mydata.class对应继承的类中,重写setList方法来对入参进行调整就可以达到效果了

{
	"list": {
      "list" : [1, 2, 3, 4, 5]
    },
	"name": "李华"
}