95

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

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

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

Java 对象:

class JavaObject {
    @NotNull
    public String notNullMember;

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

JSON 对象可以是:

{
    "notNullMember" : "notNull"
}

或者:

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

@DefaultValue注释只是为了显示我的要求。这不是真正的注释。如果 JSON 对象像第一个示例中的那样,我希望的值optionalMember"Value"而不是null。有没有做这样的事情的注释?

4

10 回答 10

97

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

public class JavaObject 
{
    public String notNullMember;

    public String optionalMember = "Value";
}
于 2013-09-14T21:54:29.483 回答
39

只有一个建议的解决方案保留了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; 
    }
}
于 2017-01-31T18:43:53.177 回答
6

您可以创建自己的 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;
 }
于 2019-06-12T13:01:34.723 回答
4

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

class JavaObject {

    public JavaObject() {

        optionalMember = "Value";
    }

    @NotNull
    public String notNullMember;

    public String optionalMember;
}

在与 Jackson 进行映射后,如果optionalMemberJSON 中缺少 ,则它在 Java 类中的值为"Value".

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

于 2013-09-14T21:15:33.473 回答
2

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

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;
}
于 2016-07-27T18:17:34.213 回答
2

另一种选择是使用InjectableValues@JacksonInject。如果您需要使用不总是相同的值,而是从数据库或其他地方获取特定情况下的值,这将非常有用。下面是一个使用示例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");
}

请记住,如果您使用构造函数来创建实体(这通常发生在您在 lombok 中使用@Value@AllArgsConstructor并且您将不是构造函数而是属性它不会按预期工作 - 注入的值字段将始终覆盖 json 中的值,无论您是否输入. 这是因为jackson在调用构造函数后注入了这些属性(即使属性是)-字段在构造函数中设置为正确的值,但随后被覆盖(检查:https ://github.com/FasterXML/jackson-databind/问题/2678https://github.com/rzwitserloot/lombok/issues/1528#issuecomment-607725333@JacksonInjectuseInput = OptBoolean.TRUE@JacksonInjectfinal有关更多信息),不幸的是,这个测试通过了:

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

另一种方法是使用JsonDeserializer,例如:

    public class DefaultValueDeserializer extends JsonDeserializer<String> {

        @Override
        public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
                throws IOException {
            return jsonParser.getText();
        }

        @Override
        public String getNullValue(DeserializationContext ctxt) {
            return "some random value that can be different each time: " + UUID.randomUUID().toString();
        }
    }

然后像这样注释一个字段:

    public class Content {
        @JsonDeserialize(using = DefaultValueDeserializer.class)
        private String someField;
...
    }

请记住,您可以在getNullValue(DeserializationContext ctxt)传递的 using中使用属性

mapper.reader().forType(SomeType.class).withAttributes(singletonMap("dbConnection", dbConnection)).readValue(jsonString);

像那样:

        @Override
        public String getNullValue(DeserializationContext ctxt) {
            return ((DbConnection)ctxt.getAttribute("dbConnection")).getDefaultValue(...);
        }

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

PS我正在使用jackson v. 2.9.6

于 2020-04-02T09:00:57.987 回答
2

使用带有值 [ ][2]的 [ JsonSetter][1] 注释Nulls.SKIP

如果您想为任何未在 json 请求中设置的参数分配默认值,那么您可以简单地在 POJO 本身中分配它。

但是,如果你不使用,@JsonSetter(nulls = Nulls.SKIP)那么只有在 JSON 中没有值的情况下才会初始化默认值,但是如果有人明确地设置了 null,那么它可能会导致问题。Using@JsonSetter(nulls = Nulls.SKIP)将告诉 Json de-searilizer 避免null初始化。

指示应跳过输入空值并进行默认分配的值;这通常意味着该属性将具有其默认值

如下:

 public class User {
    @JsonSetter(nulls = Nulls.SKIP)
    private Integer Score = 1000;
    ...
}


[1]:https://fasterxml.github.io/jackson-annotations/javadoc/2.0.0/com/fasterxml/jackson/annotation/JsonSetter.html
[2]:https://fasterxml.github.io/jackson -annotations/javadoc/2.9/com/fasterxml/jackson/annotation/Nulls.html#SKIP
于 2021-08-24T16:22:50.547 回答
1

有一个解决办法,如果你使用lombok的Builder注解,

属性上的“@Builder.default”。例如。

@Value
@Builder
@Jacksonized
public class SomeClass {

     String field1;
     
     @Builder.default
     String field2 = "default-value";

}

因此,在传入的 json 请求中,如果未指定 field2,则 Builder.default 注解将允许 Builder 接口将指定的默认值设置为属性,如果没有,则将请求中的原始值设置为该属性.

于 2021-12-09T22:48:22.027 回答
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 并设置默认值。

于 2017-12-26T15:12:48.130 回答
0

已经有很多好的建议,但这里还有一个。您可以使用@JsonDeserialize 执行杰克逊将调用后反序列化的任意“消毒剂”:

@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;
        }
    }
}
于 2020-02-05T21:05:01.133 回答