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

时间:2013-09-14 19:15:11

标签: java json jackson

我试图用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"而不是null。是否有注释可以做这样的事情?

8 个答案:

答案 0 :(得分:58)

没有注释来设置默认值 您只能在java类级别设置默认值:

public class JavaObject 
{
    public String notNullMember;

    public String optionalMember = "Value";
}

答案 1 :(得分:14)

只有一个提议的解决方案在明确设置default-value时保留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; 
    }
}

答案 2 :(得分:4)

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

class JavaObject {

    public JavaObject() {

        optionalMember = "Value";
    }

    @NotNull
    public String notNullMember;

    public String optionalMember;
}

与Jackson映射后,如果JSON中缺少optionalMember,则其在Java类中的值为"Value"

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

答案 3 :(得分:2)

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

Diff selected -> this

答案 4 :(得分: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并在字段/对象为空时设置默认值。

答案 5 :(得分:0)

您可以创建自己的JsonDeserializer并使用@JsonDeserialize(as = DefaultZero.class)

注释该属性。

f.e。对于BigDecimal的默认零值:

公共静态类DefaultZero扩展了JsonDeserializer {         私人最终JsonDeserializer委托;

    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;
    }
}

f.e。

class Sth {

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

答案 6 :(得分: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;
        }
    }
}

答案 7 :(得分: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中使用@AllArgsConstructorlombok时发生),而您没有放置@JacksonInject对于构造函数,但对于属性,它将无法按预期工作-不管是否将useInput = OptBoolean.TRUE放在@JacksonInject中,注入字段的值将始终覆盖json中的值。这是因为杰克逊会在调用构造函数后注入这些属性(即使该属性为final)-字段在构造函数中设置为正确的值,但会被覆盖(检查:https://github.com/FasterXML/jackson-databind/issues/2678和{{3 }},以获取更多信息),很遗憾,该测试已通过

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");
}

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

P.S。我正在使用杰克逊2.9.6版