【问题标题】:Setting default values to null fields when mapping with Jackson与 Jackson 映射时将默认值设置为空字段
【发布时间】:2013-09-19 06:30:28
【问题描述】:

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

在与Jackson 映射后,所有未在JSON 对象中设置的字段在Java 中都会有一个空值。是否有与@NotNull 类似的注释可以告诉Jackson 为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。有没有做这种事情的注解?

【问题讨论】:

  • 我正在寻找类似的问题,但对于最终字段。

标签: java json jackson


【解决方案1】:

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

class JavaObject {

    public JavaObject() {

        optionalMember = "Value";
    }

    @NotNull
    public String notNullMember;

    public String optionalMember;
}

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

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

【讨论】:

  • 但是如果 JSON 的值为 null,则 optionalMember 最终会为 null。这可能是想要的,也可能不是。
  • 这与仅在字段上设置默认值相同。不能解决问题。
【解决方案2】:

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

public class JavaObject 
{
    public String notNullMember;

    public String optionalMember = "Value";
}

【讨论】:

  • 如果 optionalMember 没有 json,这将给出一个默认值。如果有json将其设置为“null”,它将为null,而不是“Value”
  • 添加了一个答案,显示在null的情况下如何保存Value
  • 另外,如果类有一个AllArgsConstructor,默认值将被擦除为null 见stackoverflow.com/q/48725922/9640261
  • @LeeMeador,可能是因为在这种情况下,值是“null”?
  • @llya 不能在 jackson 2.10 中工作,有什么建议吗???
【解决方案3】:

将成员设为私有并添加一个 setter/getter 对。 在您的设置器中,如果为 null,则改为设置默认值。 此外,我还展示了带有 getter 的 sn-p,当内部值为 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;
}

【讨论】:

  • Setter 设置非空值,因此 getter 值将在每次调用时进入 if 语句当且仅当 notNullMember 从未设置过
【解决方案4】:

当明确设置some-value:null 时,只有一种建议的解决方案保留default-value(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; 
    }
}

【讨论】:

  • 也许有一种方法可以为类的所有领域做到这一点?
  • 这应该是公认的解决方案
  • 没有。先生!这段代码让你 POJO 不是纯粹的。请不要那样做。
【解决方案5】:

我遇到了类似的问题,但在我的情况下,默认值在数据库中。以下是解决方案:

 @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 时设置默认值。

【讨论】:

    【解决方案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;
     }
    

    【讨论】:

      【解决方案7】:

      已经有很多好的建议了,这里还有一个。您可以使用@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;
              }
          }
      }
      

      【讨论】:

        【解决方案8】:

        另一种选择是使用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 时)并且您将 @JacksonInject 不是构造函数而是属性它将无法按预期工作 - 注入字段的值将始终覆盖 json 中的值,无论您是否将 useInput = OptBoolean.TRUE 放入 @JacksonInject。这是因为杰克逊在调用构造函数后注入了这些属性(即使属性是final) - 字段在构造函数中设置为正确的值,但随后被覆盖(检查:https://github.com/FasterXML/jackson-databind/issues/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");
        }
        
        

        另一种方法是使用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) 传递的属性

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

        这样:

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

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

        附:我正在使用杰克逊 v. 2.9.6

        【讨论】:

          【解决方案9】:

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

          通常,如果您不使用@JsonSetter(nulls = Nulls.SKIP),那么如果 JSON 中没有值,则默认值将被初始化,但如果有人明确输入 null,则可能会导致问题。使用@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

          【讨论】:

          • 这太棒了......如果它只适用于龙目岛。
          • 它完全有效!
          • 如果值为null就完美了,但是如果值为空怎么管理呢?
          【解决方案10】:

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

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

          @Value
          @Builder
          @Jacksonized
          public class SomeClass {
          
               String field1;
               
               @Builder.default
               String field2 = "default-value";
          
          }

          所以,在传入的json请求中,如果没有指定field2,那么Builder.default注解将允许Builder接口将指定的default-value设置到属性中,如果没有,则请求的原始值是设置进去。

          【讨论】:

          • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
          猜你喜欢
          • 1970-01-01
          • 2021-09-02
          • 1970-01-01
          • 2010-11-13
          • 1970-01-01
          • 1970-01-01
          • 2014-05-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多