使用Jackson映射时,将默认值设置为空字段


78

我正在尝试使用Jackson将一些JSON对象映射到Java对象。JSON对象中的某些字段是必填字段(可以用标记@NotNull),而某些字段是可选字段。

用Jackson映射后,在JSON对象中未设置的所有字段在Java中都将具有空值。是否有类似的注释@NotNull可以告诉Jackson为Java类成员设置默认值(如果为null)?

编辑:为了使问题更清楚,这里是一些代码示例。

Java对象:

class JavaObject {
    @NotNull
    public String notNullMember;

    @DefaultValue("Value")
    public String optionalMember;
}

JSON对象可以是:

{
    "notNullMember" : "notNull"
}

要么:

{
    "notNullMember" : "notNull",
    "optionalMember" : "optional"
}

@DefaultValue注释只是为了展示一下我问。这不是真正的注释。如果JSON对象类似于第一个示例,则我希望的值optionalMember"Value"and not null。有没有做这种事情的注释?


我正在寻找类似的问题,但用于最终领域。
Daneel Yaitskov '19

Answers:


80

没有注释可以设置默认值。
您只能在Java类级别上设置默认值:

public class JavaObject 
{
    public String notNullMember;

    public String optionalMember = "Value";
}

55
如果optionalMember没有json,这将提供默认值。如果有json将其设置为“ null”,则它将为null,而不是“ Value”
Lee Meador

1
新增展示了如何保存一个答案Value的情况下null
谢尔盖沃伊托维奇

4
另外,如果该类具有AllArgsConstructor,则默认值将被擦除为null参见stackoverflow.com/q/48725922/9640261
NayoR

@LeeMeador,也许是因为在这种情况下,该值为“空”吗?
pellyadolfo

32

仅有一个建议的解决方案保留了显式设置的default-valuewhen some-value:null(POJO可读性在那里丢失并且笨拙)

这是一种可以保留default-value且从不设置为的方式null

@JsonProperty("some-value")
public String someValue = "default-value";

@JsonSetter("some-value")
public void setSomeValue(String s) {
    if (s != null) { 
        someValue = s; 
    }
}

1
也许有一种方法可以针对所有领域的学生吗?
Woland

5

您可以创建自己的JsonDeserializer并使用以下方法注释该属性 @JsonDeserialize(as = DefaultZero.class)

例如:要将BigDecimal配置为默认为零:

public static class DefaultZero extends JsonDeserializer<BigDecimal> {
    private final JsonDeserializer<BigDecimal> delegate;

    public DefaultZero(JsonDeserializer<BigDecimal> delegate) {
        this.delegate = delegate;
    }

    @Override
    public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return jsonParser.getDecimalValue();
    }

    @Override
    public BigDecimal getNullValue(DeserializationContext ctxt) throws JsonMappingException {
        return BigDecimal.ZERO;
    }
}

和用法:

class Sth {

   @JsonDeserialize(as = DefaultZero.class)
   BigDecimal property;
 }

4

看起来解决方案是在默认构造函数中设置属性的值。因此,在这种情况下,java类为:

class JavaObject {

    public JavaObject() {

        optionalMember = "Value";
    }

    @NotNull
    public String notNullMember;

    public String optionalMember;
}

用Jackson映射后,如果optionalMemberJSON中缺少,则Java类中的值为"Value"

但是,我仍然想知道是否存在带有注释且没有默认构造函数的解决方案。


6
但是,如果JSON的值为null,则optionalMember最终将为null。那可能是想要的还是不想要的。
Lee Meador

2
这与仅在字段上设置默认值相同。无法解决问题。
Innokenty

2

将成员设为私有,并添加一对setter / getter对。在您的设置器中,如果为null,则改为设置默认值。此外,我还展示了带有getter的代码段,当内部值为null时,该代码也返回默认值。

class JavaObject {
    private static final String DEFAULT="Default Value";

    public JavaObject() {
    }

    @NotNull
    private String notNullMember;
    public void setNotNullMember(String value){
            if (value==null) { notNullMember=DEFAULT; return; }
            notNullMember=value;
            return;
    }

    public String getNotNullMember(){
            if (notNullMember==null) { return DEFAULT;}
            return notNullMember;
    }

    public String optionalMember;
}

1
设置器设置了非null值,因此每次且仅当notnotNullMember从未设置时,getter值才会进入if语句中
NayoRmber 18'Nov

0

我有一个类似的问题,但就我而言,默认值在数据库中。以下是该解决方案:

 @Configuration
 public class AppConfiguration {
 @Autowired
 private AppConfigDao appConfigDao;

 @Bean
 public Jackson2ObjectMapperBuilder builder() {
   Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
       .deserializerByType(SomeDto.class, 
 new SomeDtoJsonDeserializer(appConfigDao.findDefaultValue()));
   return builder;
 }

然后,在SomeDtoJsonDeserializer使用ObjectMapper反序列化JSON和设置默认值,如果你的领域/对象为null。


0

已经有很多好的建议,但是这里还有更多。您可以使用@JsonDeserialize执行任意的“消毒程序”,Jackson将调用该程序进行反序列化:

@JsonDeserialize(converter=Message1._Sanitizer.class)  
public class Message1 extends MessageBase
{
    public String string1 = "";
    public int integer1;

    public static class _Sanitizer extends StdConverter<Message1,Message1> {
        @Override
        public Message1 convert(Message1 message) {
            if (message.string1 == null) message.string1 = "";
            return message;
        }
    }
}

0

另一个选择是使用InjectableValues@JacksonInject。如果您不总是需要使用相同的值,而是需要从DB或其他地方获取一个特定情况的值,这将非常有用。这是使用示例JacksonInject

protected static class Some {
    private final String field1;
    private final String field2;

    public Some(@JsonProperty("field1") final String field1,
            @JsonProperty("field2") @JacksonInject(value = "defaultValueForField2",
                    useInput = OptBoolean.TRUE) final String field2) {
        this.field1 = requireNonNull(field1);
        this.field2 = requireNonNull(field2);
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }
}

@Test
public void testReadValueInjectables() throws JsonParseException, JsonMappingException, IOException {
    final ObjectMapper mapper = new ObjectMapper();
    final InjectableValues injectableValues =
            new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
    mapper.setInjectableValues(injectableValues);

    final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
    assertEquals(actualValueMissing.getField1(), "field1value");
    assertEquals(actualValueMissing.getField2(), "somedefaultValue");

    final Some actualValuePresent =
            mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
    assertEquals(actualValuePresent.getField1(), "field1value");
    assertEquals(actualValuePresent.getField2(), "field2value");
}

请记住,如果你正在使用构造函数来创建实体(当你使用这个通常发生@Value@AllArgsConstructor龙目岛),你把@JacksonInject不要的构造函数,但该财产为预计将无法正常工作-注入的价值现场将始终JSON覆盖值,不管你放useInput = OptBoolean.TRUE@JacksonInject。这是因为杰克逊会在调用构造函数后注入这些属性(即使该属性为final)-字段在构造函数中设置为正确的值,但会被覆盖(请检查:https : //github.com/FasterXML/jackson-databind/ issue / 2678https://github.com/rzwitserloot/lombok/issues/1528#issuecomment-607725333有关更多信息),不幸的是,该测试通过了

protected static class Some {
    private final String field1;

    @JacksonInject(value = "defaultValueForField2", useInput = OptBoolean.TRUE)
    private final String field2;

    public Some(@JsonProperty("field1") final String field1,
            @JsonProperty("field2") @JacksonInject(value = "defaultValueForField2",
                    useInput = OptBoolean.TRUE) final String field2) {
        this.field1 = requireNonNull(field1);
        this.field2 = requireNonNull(field2);
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }
}

@Test
public void testReadValueInjectablesIncorrectBehavior() throws JsonParseException, JsonMappingException, IOException {
    final ObjectMapper mapper = new ObjectMapper();
    final InjectableValues injectableValues =
            new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
    mapper.setInjectableValues(injectableValues);

    final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
    assertEquals(actualValueMissing.getField1(), "field1value");
    assertEquals(actualValueMissing.getField2(), "somedefaultValue");

    final Some actualValuePresent =
            mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
    assertEquals(actualValuePresent.getField1(), "field1value");
    // unfortunately "field2value" is overrided because of putting "@JacksonInject" to the field
    assertEquals(actualValuePresent.getField2(), "somedefaultValue");
}

希望这对遇到类似问题的人有所帮助。

PS我正在使用杰克逊2.9.6版

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.