Spring Boot向XML元素添加属性但不在JSON响应中
Posted
技术标签:
【中文标题】Spring Boot向XML元素添加属性但不在JSON响应中【英文标题】:Spring Boot adding attribute to XML element but NOT in JSON response 【发布时间】:2020-03-09 17:55:50 【问题描述】:我正在开发一个API
,它可以同时生成 XML 和 JSON 响应。我在响应中有一个元素,它仅在 XML 响应中需要一个属性。此外,当值为null
时,该元素不应出现在两种格式的响应中。
期望: XML:
<name>john</name>
<status type="text">married</status>
JSON:
"name":"john"
"status":"married"
这是我的代码:
/**
* POJO with bunch of LOMBOK annotations to avoid boiler-plate code.
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
@Data
public class User implements Customer, Serializable
private static final long serialVersionUID = 1L;
private Status status;
private String name;
/**
* Matrital status of the user.
*/
@Builder
@Value
public static class Status
@JacksonXmlText
private String maritalStatus;
@JacksonXmlProperty(isAttribute = true)
private String type = "text";
通过上述更改,我得到了正确的 XML 响应,但 JSON 响应也返回 type=text
"status" :
"maritalStatus" : "married",
"type" : "text"
我尝试将@JsonValue
添加到private String maritalStatus
,这解决了 JSON 响应,但它通过不向元素添加属性而破坏了 XML 响应。
有人可以帮忙吗?
【问题讨论】:
您能否说明您正在使用 Lombok?不是每个人都熟悉这堆注释。此外,您可以尝试将@JsonProperty
与 xml 注释一起使用,而不是 @JsonValue
补充说我正在使用 Lombok。感谢您指出了这一点。 @JsonProperty
没有任何区别。
【参考方案1】:
在 XML 中以一种方式编组对象但在 JSON(不同字段等)中以另一种方式编组对象的解决方案是使用“mixins”。 一个技巧是你必须手动注册 mixin,没有什么魔法。见下文。
mixin接口:
public interface UserStatusXmlMixin
@JsonValue(false)
@JacksonXmlText
String getStatus();
@JacksonXmlProperty(isAttribute = true)
String getType();
实施:
@Value
public class UserStatus implements UserStatusXmlMixin
private String status;
@JsonValue
@Override
public String getStatus()
return status;
@Override
public String getType()
return "text";
/**
* Returns an unmodifiable UserStatus when status is available,
* otherwise return null. This will help to remove this object from the responses.
*/
public static UserStatus of(final String status)
return Optional.ofNullable(status)
.map(UserStatus::new)
.orElse(null);
我还必须手动注册“mixin”。
@Configuration
public class AppJacksonModule extends SimpleModule
private static final long serialVersionUID = -1;
private final Map<Class, Class> mixinByTarget;
/**
* Construct an AppJacksonModule.
*/
public AppJacksonModule()
super("AppJacksonModule");
this.mixinByTarget = Map.of(
UserStatus.class, UserStatusXmlMixin.class
);
@Override
public void setupModule(final SetupContext context)
super.setupModule(context);
final ObjectCodec contextOwner = context.getOwner();
if (contextOwner instanceof XmlMapper)
mixinByTarget.forEach(context::setMixInAnnotations);
现在,如果输入参数为空,我需要使用 UserStatus.of(..)
创建 UserStatus,<status/>
将不会出现在响应中。
【讨论】:
Optional.ofNullable(status).map(UserStatus::new).orElse(null)
?或许我老了,但怎么比status == null ? null : new UserStatus(status)
好?【参考方案2】:
可能最简单的方法是为User.Status
实现自定义序列化程序,并为不同类型的表示生成不同的输出。
class UserStatusJsonSerializer extends JsonSerializer<User.Status>
@Override
public void serialize(User.Status value, JsonGenerator gen, SerializerProvider serializers) throws IOException
if (gen instanceof ToXmlGenerator)
ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
serializeXml(value, toXmlGenerator);
else
gen.writeString(value.getMaritalStatus());
private void serializeXml(User.Status value, ToXmlGenerator toXmlGenerator) throws IOException
toXmlGenerator.writeStartObject();
toXmlGenerator.setNextIsAttribute(true);
toXmlGenerator.writeFieldName("type");
toXmlGenerator.writeString(value.getType());
toXmlGenerator.setNextIsAttribute(false);
toXmlGenerator.writeRaw(value.getMaritalStatus());
toXmlGenerator.writeEndObject();
@Override
public boolean isEmpty(SerializerProvider provider, User.Status value)
return value == null || value.getMaritalStatus() == null;
从现在开始,您可以移除多余的XML
注解并注册自定义序列化器:
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
@Data
class User implements Serializable
private static final long serialVersionUID = 1L;
private Status status;
private String name;
@Builder
@Value
@JsonSerialize(using = UserStatusJsonSerializer.class)
public static class Status
private String maritalStatus;
private String type = "text";
简单的控制台应用程序用法如下所示:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
public class JsonPathApp
public static void main(String[] args) throws Exception
List<User> users = Arrays.asList(
createUser("John", "married"),
createUser("Tom", null));
ObjectMapper jsonMapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.build();
for (User user : users)
System.out.println(jsonMapper.writeValueAsString(user));
System.out.println();
XmlMapper xmlMapper = XmlMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.build();
for (User user : users)
System.out.println(xmlMapper.writeValueAsString(user));
System.out.println();
private static User createUser(String name, String maritalStatus)
return User.builder()
.name(name)
.status(User.Status.builder()
.maritalStatus(maritalStatus)
.build())
.build();
上面的代码打印
John 的 JSON:
"status" : "married",
"name" : "John"
Tom 的 JSON:
"name" : "Tom"
约翰的 XML:
<User>
<status type="text">married</status>
<name>John</name>
</User>
汤姆的 XML
<User>
<name>Tom</name>
</User>
注意,我们实现了UserStatusJsonSerializer#isEmpty
方法,它定义了empty
对Status
类的含义。现在,我们需要在您的Spring Boot
应用程序中启用JsonInclude.Include.NON_EMPTY
功能。将以下键添加到您的应用程序配置文件中:
spring.jackson.default-property-inclusion=non_empty
如果您不想全局启用包含,则可以使用 @JsonInclude
注释仅对一个属性启用它。
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Status status;
另见:
Using Jackson to add XML attributes to manually-built node-tree How to tell Jackson to ignore a field during serialization if its value is null? Spring Boot: Customize the Jackson ObjectMapper【讨论】:
谢谢,米哈尔。我找到了解决此问题的另一种方法。但是如果我的“mixin”方式失败,我将尝试使用自定义序列化程序的方式。看我的回答。以上是关于Spring Boot向XML元素添加属性但不在JSON响应中的主要内容,如果未能解决你的问题,请参考以下文章
如果 xml 元素命名约定与 POJO 属性命名约定不同,则发送到 Spring Boot REST API 的 XML 元素不会映射到 POJO
如果xml元素命名约定与POJO属性命名约定不同,则发送到Spring Boot REST API的XML元素不会映射到POJO