Ich versuche, einige JSON-Objekte mit Jackson Java-Objekten zuzuordnen. Einige der Felder im JSON-Objekt sind obligatorisch (mit denen ich markieren kann @NotNull
), andere sind optional.
Nach der Zuordnung mit Jackson haben alle Felder, die nicht im JSON-Objekt festgelegt sind, in Java einen Nullwert. Gibt es eine ähnliche Anmerkung @NotNull
, die Jackson anweisen kann, einen Standardwert für ein Java-Klassenmitglied festzulegen, falls dieser null ist?
Bearbeiten: Um die Frage klarer zu machen, hier ein Codebeispiel.
Das Java-Objekt:
class JavaObject {
@NotNull
public String notNullMember;
@DefaultValue("Value")
public String optionalMember;
}
Das JSON-Objekt kann entweder:
{
"notNullMember" : "notNull"
}
oder:
{
"notNullMember" : "notNull",
"optionalMember" : "optional"
}
Die @DefaultValue
Anmerkungen sollen nur zeigen, was ich frage. Es ist keine echte Anmerkung. Wenn das JSON-Objekt dem ersten Beispiel entspricht, soll der Wert von optionalMember
sein "Value"
und nicht null
. Gibt es eine Anmerkung, die so etwas tut?
Antworten:
Es gibt keine Anmerkung zum Festlegen des Standardwerts.
Sie können den Standardwert nur auf Java-Klassenebene festlegen:
public class JavaObject { public String notNullMember; public String optionalMember = "Value"; }
quelle
Value
im Fall vonnull
Nur eine vorgeschlagene Lösung behält das explizit festgelegte
default-value
Wannsome-value:null
bei (die POJO-Lesbarkeit geht dort verloren und ist ungeschickt).So kann man das behalten
default-value
und nie einstellennull
@JsonProperty("some-value") public String someValue = "default-value"; @JsonSetter("some-value") public void setSomeValue(String s) { if (s != null) { someValue = s; } }
quelle
Sie können Ihren eigenen JsonDeserializer erstellen und diese Eigenschaft mit Anmerkungen versehen
@JsonDeserialize(as = DefaultZero.class)
Beispiel: So konfigurieren Sie BigDecimal standardmäßig auf NULL:
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; } }
Und Verwendung:
class Sth { @JsonDeserialize(as = DefaultZero.class) BigDecimal property; }
quelle
Anscheinend besteht die Lösung darin, den Wert der Eigenschaften im Standardkonstruktor festzulegen. In diesem Fall lautet die Java-Klasse also:
class JavaObject { public JavaObject() { optionalMember = "Value"; } @NotNull public String notNullMember; public String optionalMember; }
Wenn nach dem Mapping mit Jackson das
optionalMember
im JSON fehlt, ist sein Wert in der Java-Klasse"Value"
.Ich bin jedoch immer noch interessiert zu wissen, ob es eine Lösung mit Anmerkungen und ohne den Standardkonstruktor gibt.
quelle
Machen Sie das Mitglied privat und fügen Sie ein Setter / Getter-Paar hinzu. Wenn in Ihrem Setter null, setzen Sie stattdessen den Standardwert. Zusätzlich habe ich das Snippet gezeigt, wobei der Getter auch einen Standardwert zurückgibt, wenn der interne Wert null ist.
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; }
quelle
Ich hatte ein ähnliches Problem, aber in meinem Fall war der Standardwert in der Datenbank. Unten ist die Lösung dafür:
@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; }
Wird dann
SomeDtoJsonDeserializer
verwendet,ObjectMapper
um den JSON zu deserialisieren und den Standardwert festzulegen, wenn Ihr Feld / Objekt null ist.quelle
Es gibt bereits viele gute Vorschläge, aber hier noch einer. Sie können @JsonDeserialize verwenden, um ein beliebiges "Desinfektionsmittel" auszuführen, das Jackson nach der Deserialisierung aufruft:
@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; } } }
quelle
Eine andere Option ist die Verwendung von InjectableValues und @JacksonInject . Es ist sehr nützlich, wenn Sie nicht immer den gleichen Wert verwenden müssen, sondern einen von DB oder einem anderen Ort für den speziellen Fall erhalten. Hier ist ein Beispiel für die Verwendung von
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"); }
Denken Sie daran, dass wenn Sie den Konstruktor zum Erstellen der Entität verwenden (dies geschieht normalerweise, wenn Sie @Value oder @AllArgsConstructor in lombok verwenden ) und Sie
@JacksonInject
nicht den Konstruktor, sondern die Eigenschaft verwenden, funktioniert dies nicht wie erwartet - Wert des injizierten Feld wird immer Wert in json außer Kraft setzen, egal ob Sie setzenuseInput = OptBoolean.TRUE
in@JacksonInject
. Dies liegt daran, dass Jackson diese Eigenschaften nach dem Aufruf des Konstruktors einfügt (auch wenn die Eigenschaft lautetfinal
). Das Feld wird im Konstruktor auf den richtigen Wert gesetzt, aber dann überschrieben (siehe: https://github.com/FasterXML/jackson-databind/). Issues / 2678 und https://github.com/rzwitserloot/lombok/issues/1528#issuecomment-607725333für weitere Informationen), wird dieser Test leider vorbei :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"); }
Hoffe das hilft jemandem mit einem ähnlichen Problem.
PS Ich benutze Jackson v. 2.9.6
quelle