Jackson + Builder Pattern?

89

Chciałbym, aby Jackson deserializował klasę za pomocą następującego konstruktora:

public Clinic(String name, Address address)

Deserializacja pierwszego argumentu jest łatwa. Problem polega na tym, że adres jest zdefiniowany jako:

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

i jest zbudowany w następujący sposób: new Address.Builder().setCity("foo").setCountry("bar").create();

Czy istnieje sposób na uzyskanie par klucz-wartość od Jacksona w celu samodzielnego skonstruowania adresu? Alternatywnie, czy jest sposób, aby Jackson użył samej klasy Builder?

Gili
źródło

Odpowiedzi:

139

Jeśli używasz Jackson 2+, masz teraz wbudowaną obsługę tego rozwiązania .

Najpierw musisz dodać tę adnotację do swojej Addressklasy:

@JsonDeserialize(builder = Address.Builder.class)

Następnie musisz dodać tę adnotację do swojej Builderklasy:

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

Możesz pominąć tę drugą adnotację, jeśli z przyjemnością zmienisz nazwę metody tworzenia swojego Konstruktora na kompilację, a ustawiacze twojego Konstruktora mają być poprzedzone prefiksem na zamiast set.

Pełny przykład:

@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
źródło
14
Jeśli chcesz pozbyć się @JsonPOJOBuilderwszystkich adnotacji razem, zmień nazwę „utwórz” na „buduj” i dodaj adnotację do każdego ustawiacza budującego @JsonProperty.
Sam Berry,
to jest złote. Dzięki.
Mukul Goel
To jest teraz przestarzałe, z Lombok 1.18.4 możesz użyć, @Jacksonizedktóry zastępuje wewnętrznego konstruktora i adnotacje
Jacksona
@Randakar Nie wydaje mi się, żeby to było przestarzałe, ponieważ a) @Jackonized jest właśnie wydaną eksperymentalną funkcją w Lombok. Nie sądzę, aby niepotrzebne zachęcanie do przyjmowania funkcji eksperymentalnych było dobrym pomysłem. b) pytanie nie wspomina ani nie używa Lombok. Uważam, że niepotrzebne wprowadzanie zależności w celu rozwiązania problemu nie jest dobrym pomysłem.
Rupert Madden-Abbott
19

Odpowiedź od @Rupert Madden-Abbott działa. Jeśli jednak masz konstruktor inny niż domyślny, np.

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

Następnie powinieneś opisać parametry jak poniżej:

@JsonCreator
Builder(@JsonProperty("city")    String city, 
        @JsonProperty("country") String country) {...}
volatilevar
źródło
9

Rozwiązanie, które było dla mnie odpowiednie w tym przypadku (użyłem adnotacji budowniczego "Lombok").

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

Mam nadzieję, że tobie też się przyda.

JustK K
źródło
To jest teraz przestarzałe, z Lombok 1.18.4 możesz użyć, @Jacksonizedktóry zastępuje wewnętrznego konstruktora i adnotacje
Jacksona
7

Skończyło się na implementacji tego przy użyciu @JsonDeserialize w następujący sposób:

@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
źródło
Być może w ten sposób udało Ci się to zaimplementować, ale ta odpowiedź w rzeczywistości nie odpowiada na postawione pytanie. Odpowiedź przesłana przez @Rupert Madden-Abbott powinna być oznaczona jako zaakceptowana.
kelnos
2

Obecnie nie ma wsparcia dla wzorca buildera, chociaż został o to poproszony już jakiś czas temu (i wreszcie został zgłoszony numer Jira http://jira.codehaus.org/browse/JACKSON-469 ) - to coś, co można dodać na wydanie 1.8, jeśli jest wystarczające zapotrzebowanie (koniecznie zagłosuj w Jira!). Jest to rozsądna dodatkowa funkcja, opóźniona tylko o czas, jaki mają programiści. Ale myślę, że byłby to świetny dodatek.

StaxMan
źródło
2
Codehaus nie ma już dostępnej Jira, ale powiązany problem jest opisany tutaj: wiki.fasterxml.com/JacksonFeatureBuilderPattern
Paul
Wsparcie dla wzorca Builder zostało dodane dawno temu, na przykład Jackson 2.2.
StaxMan
2

To zadziałało dla mnie: @NoArgsConstructor Jedyną wadą tego jest to, że można ponownie wykonać = new ADTO (). Ale, hej, i tak nie lubię policji kodującej, mówiąc mi, jak używać czyjegoś kodu :-) Więc używaj mojego POJO DTOS tak, jak lubisz. Z konstruktorem lub bez niego. Proponuję: zrób to z Konstruktorem, ale bądź moim gościem ...

@Data
@Builder
//Dont forget this! Otherwise no Jackson serialisation possible!
@NoArgsConstructor
@AllArgsConstructor
public class ADTO {
.....
}
Roland Roos
źródło
Nie lubisz wiedzieć, jak używać czyjegoś kodu?
masterxilo