Une autre option consiste à utiliser InjectableValues y @JacksonInject . C'est très utile si vous avez besoin d'utiliser une valeur qui n'est pas toujours la même mais qui provient de la base de données ou d'un autre endroit pour un cas spécifique. Voici un exemple d'utilisation de 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");
}
Gardez à l'esprit que si vous utilisez le constructeur pour créer l'entité (ce qui se produit généralement lorsque vous utilisez la fonction @Value o @AllArgsConstructor sur lombok ) et vous mettez @JacksonInject
non pas au constructeur mais à la propriété, cela ne fonctionnera pas comme prévu - la valeur du champ injecté remplacera toujours la valeur dans le json, peu importe que vous mettiez le champ useInput = OptBoolean.TRUE
sur @JacksonInject
. Ceci est dû au fait que jackson injecte ces propriétés après l'appel du constructeur (même si la propriété est final
) - le champ est défini à la valeur correcte dans le constructeur mais il est ensuite remplacé (vérification : https://github.com/FasterXML/jackson-databind/issues/2678 y https://github.com/rzwitserloot/lombok/issues/1528#issuecomment-607725333 pour plus d'informations), ce test est malheureusement en passant par :
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");
}
Une autre approche consiste à utiliser JsonDeserializer
par exemple :
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();
}
}
et ensuite annoter un champ comme ça :
public class Content {
@JsonDeserialize(using = DefaultValueDeserializer.class)
private String someField;
...
}
n'oubliez pas que vous pouvez utiliser des attributs dans getNullValue(DeserializationContext ctxt)
passé en utilisant
mapper.reader().forType(SomeType.class).withAttributes(singletonMap("dbConnection", dbConnection)).readValue(jsonString);
comme ça :
@Override
public String getNullValue(DeserializationContext ctxt) {
return ((DbConnection)ctxt.getAttribute("dbConnection")).getDefaultValue(...);
}
J'espère que cela aidera quelqu'un ayant un problème similaire.
P.S. J'utilise jackson v. 2.9.6