Здравствуйте!
Недавно в своем учебном Spring проекте Java Enterprise (Topjava) столкнулся с задачей каcтомизации сериализации объекта User в JSON в зависимости от контроллера: для REST API контроллера нужно было возвращать хешированный пароль (поле
user.password
), а для контроллера отображения на UI- нет. Можно решить задачу в «лоб», сделав нестолько TO (Data Transfer Object), но в Spring 4.2+/Jackson 2.6 появилась возможность использовать Jackson’s Serialization Views. Однако с статье есть подвох, и для невнимательных читателей вьюхи работают не так, как он ожидает. В результате мне пришлось немного покопаться в реализации Jackson, чтобы понять, как все это работает. Коротко об этом:
MapperFeature.DEFAULT_VIEW_INCLUSION
В статье есть небольшое упоминание
In Spring MVC default configuration, MapperFeature.DEFAULT_VIEW_INCLUSION is set to false.
Это означает, что по умолчанию поля, не помеченные аннотацией
@JsonView
, исключаются. Но если посмотреть в код MapperFeature
, то увидим: ...
* Default value is enabled, meaning that non-annotated
* properties are included in all views if there is no
* {@link com.fasterxml.jackson.annotation.JsonView} annotation.
*
* Feature is enabled by default.
*/
DEFAULT_VIEW_INCLUSION(true),
Т.е все с точностью до наоборот — все, что не помечено, включается. И если пометить только нужные для UI поля User:
public class User
...
@JsonView(View.UI.class)
protected String email;
@JsonView(View.UI.class)
protected boolean enabled = true;
protected String password;
и вызвать помеченный
@JsonView
метод контроллера@JsonView(View.UI.class)
@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public User get(@PathVariable("id") int id) {
return ...;
}
То в результат войдут как помеченные поля User (
email, enabled, ..
), так и все остальные (password
).FilteredBeanPropertyWriter
Т.к. хочется исключить из контроллера UI только одно поле
password
, логично будет пометить только его. Смотрим в код jackson-databind-2.8.0
: если запрос контроллера и поля его результата аннотированы @JsonView
, Jackson сериализует через FilteredBeanPropertyWriter.serializeAsField
final Class<?> activeView = prov.getActiveView();
if (activeView != null) {
int i = 0, len = _views.length;
for (; i < len; ++i) {
if (_views[i].isAssignableFrom(activeView)) break;
}
// not included, bail out:
if (i == len) {
_delegate.serializeAsOmittedField(bean, jgen, prov);
return;
}
}
_delegate.serializeAsField(bean, jgen, prov);
Т.е. если View, которым помечено поле объекта, совпадает или является суперклассом от View метода контроллера, поле сериализуется. Иначе оно пропускается (
serializeAsOmittedField
).Решение
В итоге:
- создаем по одному View для каждого контекста сериализации
public class View { public static class REST {} public static class UI {} }
- помечаем в User исключаемые в UI поля тем View, в котором они должны присутствовать (REST)
public class User ... protected String email; protected boolean enabled = true; @JsonView(View.REST.class) protected String password;
- аннотируем метод контроллера UI соответствующим контекстом
@JsonView(View.UI.class) @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public User get(@PathVariable("id") int id) { return ...; }
Теперь поле
password
в результат не попадет. В контроллере REST можно обойтись без @JsonView
, т.к. туда включаются все поля User.Спасибо за внимание! Надеюсь
@JsonView
сделают Ваши Spring приложения более красивыми и компактными.