Jackson + Builder-Muster?

88

Ich möchte, dass Jackson eine Klasse mit dem folgenden Konstruktor deserialisiert:

public Clinic(String name, Address address)

Das erste Argument zu deserialisieren ist einfach. Das Problem ist, dass die Adresse wie folgt definiert ist:

public class Address {
  private Address(Map<LocationType, String> components)
  ...

  public static class Builder {
    public Builder setCity(String value);
    public Builder setCountry(String value);
    public Address create();
  }
}

und ist so aufgebaut: new Address.Builder().setCity("foo").setCountry("bar").create();

Gibt es eine Möglichkeit, Schlüssel-Wert-Paare von Jackson zu erhalten, um die Adresse selbst zu erstellen? Gibt es eine Möglichkeit, Jackson dazu zu bringen, die Builder-Klasse selbst zu verwenden?

Gili
quelle

Antworten:

137

Solange Sie Jackson 2+ verwenden, wird dies jetzt unterstützt .

Zuerst müssen Sie diese Anmerkung zu Ihrer AddressKlasse hinzufügen :

@JsonDeserialize(builder = Address.Builder.class)

Dann müssen Sie diese Anmerkung zu Ihrer BuilderKlasse hinzufügen :

@JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")

Sie können diese zweite Anmerkung überspringen, wenn Sie die Erstellungsmethode Ihres Builders zum Erstellen umbenennen möchten und die Setter Ihres Builders anstelle von set vorangestellt werden sollen.

Vollständiges Beispiel:

@JsonDeserialize(builder = Address.Builder.class)
public class Address
{
  private Address(Map<LocationType, String> components)
  ...

  @JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")
  public static class Builder
  {
    public Builder setCity(String value);
    public Builder setCountry(String value);
    public Address create();
  }
}
Rupert Madden-Abbott
quelle
14
Wenn Sie die @JsonPOJOBuilderAnnotation insgesamt entfernen möchten, benennen Sie "create" in "build" um und kommentieren Sie jeden der Builder-Setter mit @JsonProperty.
Sam Berry
das ist golden. Vielen Dank.
Mukul Goel
19

Die Antwort von @Rupert Madden-Abbott funktioniert. Wenn Sie jedoch einen nicht standardmäßigen Konstruktor haben, z.

Builder(String city, String country) {...}

Dann sollten Sie die Parameter wie folgt kommentieren:

@JsonCreator
Builder(@JsonProperty("city")    String city, 
        @JsonProperty("country") String country) {...}
volatilevar
quelle
7

Am Ende habe ich dies mit @JsonDeserialize wie folgt implementiert:

@JsonDeserialize(using = JacksonDeserializer.class)
public class Address
{...}

@JsonCachable
static class JacksonDeserializer extends JsonDeserializer<Address>
{
    @Override
    public Address deserialize(JsonParser parser, DeserializationContext context)
        throws IOException, JsonProcessingException
    {
        JsonToken token = parser.getCurrentToken();
        if (token != JsonToken.START_OBJECT)
        {
            throw new JsonMappingException("Expected START_OBJECT: " + token, parser.getCurrentLocation());
        }
        token = parser.nextToken();
        Builder result = new Builder();
        while (token != JsonToken.END_OBJECT)
        {
            if (token != JsonToken.FIELD_NAME)
            {
                throw new JsonMappingException("Expected FIELD_NAME: " + token, parser.getCurrentLocation());
            }
            LocationType key = LocationType.valueOf(parser.getText());

            token = parser.nextToken();
            if (token != JsonToken.VALUE_STRING)
            {
                throw new JsonMappingException("Expected VALUE_STRING: " + token, parser.getCurrentLocation());
            }
            String value = parser.getText();

            // Our Builder allows passing key-value pairs
            // alongside the normal setter methods.
            result.put(key, value);
            token = parser.nextToken();
        }
        return result.create();
    }
}
Gili
quelle
Auf diese Weise haben Sie es möglicherweise implementiert, aber diese Antwort beantwortet die gestellte Frage nicht wirklich. Die Antwort von @Rupert Madden-Abbott sollte als akzeptiert markiert werden.
Kelnos
7

Eine Lösung, die in diesem Fall für mich geeignet war (ich habe die Builder-Annotation "Lombok" verwendet).

@Getter
@Builder(builderMethodName = "builder")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    creatorVisibility = JsonAutoDetect.Visibility.ANY
)

Ich hoffe, es wäre auch für dich nützlich.

JustK K.
quelle
2

Derzeit gibt es keine Unterstützung für das Builder-Muster, obwohl es vor einiger Zeit angefordert wurde (und schließlich wurde das Jira-Problem http://jira.codehaus.org/browse/JACKSON-469 eingereicht) - es kann hinzugefügt werden für 1.8 Release, wenn genügend Nachfrage besteht (stimmen Sie bei Jira ab!). Es ist eine vernünftige zusätzliche Funktion und wird nur durch die Zeit verzögert, die Entwickler haben. Aber ich denke, es wäre eine großartige Ergänzung.

StaxMan
quelle
2
Codehaus hat Jira nicht mehr verfügbar, aber das verknüpfte Problem wird hier beschrieben: wiki.fasterxml.com/JacksonFeatureBuilderPattern
Paul
Die Unterstützung für das Builder-Muster wurde bei Jackson 2.2 längst hinzugefügt.
StaxMan
2

Das hat bei mir funktioniert: @NoArgsConstructor Der einzige Nachteil dabei ist, dass man = new ADTO () wieder machen kann. Aber hey, ich mag die Code-Polizei sowieso nicht und sage mir, wie man jemandes Code verwendet :-) Also, benutze mein POJO DTOS so, wie du es magst. Mit oder ohne Builder. Ich schlage vor: mach es mit einem Baumeister, aber sei mein Gast ...

@Data
@Builder
//Dont forget this! Otherwise no Jackson serialisation possible!
@NoArgsConstructor
@AllArgsConstructor
public class ADTO {
.....
}
Roland Roos
quelle
Sie möchten nicht wissen, wie man den Code einer Person verwendet?
masterxilo