Шаблон Джексон + Строитель?


89

Я бы хотел, чтобы Джексон десериализовал класс с помощью следующего конструктора:

public Clinic(String name, Address address)

Десериализовать первый аргумент несложно. Проблема в том, что Address определяется как:

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

и устроен так: new Address.Builder().setCity("foo").setCountry("bar").create();

Есть ли способ получить пары ключ-значение от Джексона, чтобы самому создать адрес? В качестве альтернативы, есть ли способ заставить Джексона использовать сам класс Builder?

Ответы:


139

Пока вы используете Jackson 2+, теперь для этого есть встроенная поддержка .

Сначала вам нужно добавить эту аннотацию в свой Addressкласс:

@JsonDeserialize(builder = Address.Builder.class)

Затем вам нужно добавить эту аннотацию в свой Builderкласс:

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

Вы можете пропустить эту вторую аннотацию, если вы хотите переименовать метод create вашего Builder в build, а сеттеры вашего Builder будут иметь префикс с вместо set.

Полный пример:

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

14
Если необходимо полностью избавиться от @JsonPOJOBuilderаннотации, переименуйте «create» в «build» и аннотируйте каждый из установщиков построителя с помощью @JsonProperty.
Сэм Берри,

это золото. Спасибо.
Мукул Гоэль

Теперь это устарело, вы можете использовать Lombok 1.18.4, @Jacksonizedкоторый заменяет внутренний конструктор и аннотации
Джексона

@Randakar Я не думаю, что это устарело, потому что а) @Jackonized - это только что выпущенная экспериментальная функция в Lombok. Я не думаю, что без надобности поощрять внедрение экспериментальных функций - хорошая идея. б) в вопросе не упоминается и не используется Ломбок. Я не думаю, что излишне вводить зависимость для решения проблемы - хорошая идея.
Руперт Мэдден-Эбботт,

19

Ответ от @Rupert Madden-Abbott работает. Однако, если у вас есть конструктор не по умолчанию, например,

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

Затем вы должны аннотировать параметры, как показано ниже:

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

9

Решение, которое мне подошло в данном случае (я использовал аннотацию конструктора "Lombok").

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

Надеюсь, тебе тоже будет полезно.


Теперь это устарело, вы можете использовать Lombok 1.18.4, @Jacksonizedкоторый заменяет внутренний конструктор и аннотации
Джексона

7

В итоге я реализовал это с помощью @JsonDeserialize следующим образом:

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

Возможно, именно так вы его реализовали, но этот ответ на самом деле не отвечает на поставленный вопрос. Ответ, отправленный @Rupert Madden-Abbott, должен быть отмечен как принятый.
kelnos

2

В настоящее время нет поддержки для шаблона компоновщика, хотя он был запрошен довольно давно (и, наконец, была подана проблема Jira http://jira.codehaus.org/browse/JACKSON-469 ) - это то, что можно добавить для выпуска 1.8, если есть достаточный спрос (обязательно проголосуйте в Jira!). Это разумная дополнительная функция, которую откладывают только разработчики. Но думаю, это было бы отличным дополнением.


2
В Codehaus больше нет Jira, но связанная проблема описана здесь: wiki.fasterxml.com/JacksonFeatureBuilderPattern
Paul

Поддержка шаблона Builder была добавлена ​​давно, в нечто вроде Jackson 2.2.
StaxMan

2

У меня это сработало: @NoArgsConstructor Единственный недостаток в том, что можно снова сделать = new ADTO (). Но, эй, мне все равно не нравится de code police, рассказывающая мне, как использовать чей-то код :-) Итак, используйте мои POJO DTOS так, как вам нравится. Со строителем или без него. Предлагаю: сделай это Строителем, но будь моим гостем ...

@Data
@Builder
//Dont forget this! Otherwise no Jackson serialisation possible!
@NoArgsConstructor
@AllArgsConstructor
public class ADTO {
.....
}

Вам не нравится знать, как использовать чей-то код?
masterxilo
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.