
Здравствуйте!
Недавно в своем учебном 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.serializeAsFieldfinal 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 приложения более красивыми и компактными.