Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程

文章目录

    • 场景描述
    • 示例说明
    • 解决历程
        • @JsonFormat是否能解决问题?
        • 万能方案-调试
    • 替代方案
    • 补充知识
        • Java对象与JSON对象的序列化与反序列化
        • 相关注解说明
        • 后记

场景描述

在Spring Boot项目中, 很多场景是将一个实体对象返回为JSON格式的字串, 但是在返回时需要指定一个String类型的属性返回的是Boolean类型的值。

示例说明

举例来说:定义了一个Animal类,该类有两个属性 name(动物的名字 )和extinct(该动物是否灭绝)。该类的定义如下:

public class Animal {
	private String name;
	private String extinct;

       //属性setter、getter方法
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getExtinct() {
		return extinct;
	}

	public void setExtinct(String extinct) {
		this.extinct = extinct;
	}
}

定义一个REST类型的控制器类JsonController , 该类中有一个接口方法str2bool(),方法返回一个Animal对象,代码如下:

@RestController
@RequestMapping(value = "/restjson")
public class JsonController {

	@GetMapping(value = "/str2bool")
	public Animal str2bool() {
		Animal tiger = new Animal();
		tiger.setName("Tiger");
		tiger.setExtinct("false");
		return tiger;
	}

}

以为以上控制器使用@RestController
注解,所以返回的是一个JSON字符串,启动项目之后,在浏览器中输入: http://localhost:8080/restjson/str2bool, 返回的页面如下:
Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程_第1张图片
注意以上返回的"false",在Spring Boot配合前端框架开发时, 比如Ext JS, 有时候对于返回的数据类型有特殊要求,比如某些组件(比如Checkbox)对数据类型是敏感的, 也就是说 "false"和 fasle是严格区分的, 虽然也可以通过前端进行类新的转换, 但如果后端服务直接返回相应的类型看上去更简单。

解决历程

@JsonFormat是否能解决问题?

将实体类的String类型的属性值返回JSON的布尔类型的键值,首选想到的就是@JsonFormat这个注解,而且查看JsonFormat注解的相关说明,的确是有一个shape属性用来指定类型,于是乎在 extinct 属性上加上该注解:

	@JsonFormat(shape=Shape.BOOLEAN)
	private String extinct;

可是修改完成之后,重启运行, 没有效果, 返回的值依旧是"false", 不是false。 难道是不支持从字符串类型到布尔型的转换吗?
那整型的转换支持吗?使用Shape.NUMBER,

	@JsonFormat(shape = Shape.NUMBER)
	private String extinct;
在str2bool()方法中赋一个数字的字符串`tiger.setExtinct("123");` 之后发现,依旧无效。

@JsonFormat是否能解决问题? 结果是不行

万能方案-调试

百度乃至千度无果的状况下, 只能调试了。可问题是: 调试从何入口?于是思考起来:

Spring Boot 默认使用Jackson处理数据转换,而且Spring Boot默认提供了Jackon的消息转换器
这个消息转换器类是MappingJackson2HttpMessageConverter,位于spring-web的包中,查看该类发现,只有两个构造方法和setter方法, 看起来处理方法是在父类中,果不其然,在父类AbstractJackson2HttpMessageConverter中找到
writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)方法,这个方法调用的 objectWriter.writeValue(generator, value); 看起来就是在处理JOSN输出, 代码如下:
Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程_第2张图片
进一步追踪

  1. writeValue(JsonGenerator g, Object value) 是 Jackson的
    ObjectWriter类中的方法,翻译一下“对象写出器”,类名看起来就是对Java对象进行转换,输出。
  2. writeValue()方法会调用 serialize(JsonGenerator gen, Object value, DefaultSerializerProvider prov), 这个方法是序列化对象无疑。
  3. ObjectWriter的serialize()方法会调用DefaultSerializerProvider(默认序列化器的供应器)的serializeValue(JsonGenerator gen, Object value)方法。
  4. DefaultSerializerProvider 再调用BeanSerializer转换Java Bean。
  5. BeanSerializer中的serializeFields()方法转换属性, 调用BeanPropertyWriter 的serializeAsField()方法,
  6. BeanPropertyWriter调用不同的标量转换器比如 StringSerializer进行转换。
    以上调试的结论有两个关键点:
  7. JsonSerializer(JSON序列化器) ,这是一个抽象类 , 其有一个重要的子类StdSerializer(标准序列化器), 主要子类包含BeanSerializerBase、ByteArraySerializer、CharArraySerializer以及StdScalarSerializer(标准标量序列化器)。
    StdScalarSerializer就包含BooleanSerializer、StringSerializer等基本类型的转换。
  8. 上面使用的是StringSerializer对属性进行序列化, 所以结果是字符串 “false”。

基于上面调试的过程和结论, 于是就有个想法: 是否有Serializer相关的注解,指定序列化器呢?
如果有,应该是位于Jackson的databind相关的包中,的确,在jackson-databind中找到了 JsonSerialize注解标签,该标签的using属性看起来是指定序列化器的类型。
Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程_第3张图片
于是, 有个假设是使用JsonSerialize注解使用BooleanSerializer作为序列化器是否可以呢?属性上加上如下注解:

@JsonSerialize(using = BooleanSerializer.class)
private String extinct;

重新启动应用之后,访问却报如下错误:
Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程_第4张图片

No converter found for return value of type: class cn.osxm.springboot.model.Animal

大致意思没有Animal类的转化器。
Spring的数据绑定和转换器是对应的。这里就没有再细究,直接百度一下@JsonSerialize的用法,得到的结论是需要自定义序列器类, 于是定义一个试试, 这里自定义的序列化器的类是:StringToBoolSerializer,内容如下:

public class StringToBoolSerializer extends JsonSerializer<String> {
     

	@Override
	public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
			throws IOException {
     
		jsonGenerator.writeBoolean(str.equalsIgnoreCase("true"));
	}

}

对应,属性的注解修改如下:

     @JsonSerialize(using = StringToBoolSerializer.class)
     private String invisible;

重启运行, 可以了。目标达成。

Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程_第5张图片

替代方案

这里纠结要转换的原因是这个问题出现在一个既有的项目中, 其实如果Java类的属性定义为boolean类型,则转换为JSON后的值就是Boolean类型了。(在笔者的既有项目中更换类型会导致启动失败)

	private boolean extinct;
	public boolean isExtinct() {
     
		return extinct;
	}
	public void setExtinct(boolean extinct) {
     
		this.extinct = extinct;
	}

而且在boolean类型属性上使用@JsonFormat(shape = Shape.NUMBER)注解也是有效的,false会被转换为 0 .

补充知识

Java对象与JSON对象的序列化与反序列化

调试过程中这两个单词会高频出现:

  • serializer - 序列化
  • Deserializer - 反序列化

Java对象的序列化与反序列化的概念如下:

  • 序列化:Java对象写入IO流
  • 反序列化:从IO流再恢复Java对象
    转换为IO流之后, 可以写入文件或进行网络传输。

在Java Web开发中, JSON数据格式是规范的前端数据格式,借由序列化相关的概念:
Java对象==》JSON对象, 称之为序列化
JSON对象==》Java对象, 称之为反序列化

相关注解说明

  • @JsonFormat, 使用在属性或方法上,可以用于把日期类型转换为指定格式的日期字符串,比如@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="MM-dd-yyyy")
  • @JsonIgnoreProperties, 忽略需要转换的属性,使用在类上
  • @JsonIgnore, 忽略需要转换的属性,使用在属性或者方法上
  • @JsonSerialize, 使用在属性或getter方法上,在序列化时使用自定义的器转换属性值,比如字符串转布尔, 或者任意的转换逻辑。
  • @JsonDeserialize,使用在属性或setter方法上,在反序列化时使用自定义的器转换属性值

后记

基于以上分析, 本篇取以下标题最为合适:
Java序列化为JSON对象时,String类型属性值转换为Boolean类型的处理。

你可能感兴趣的:(Spring,Boot)