I think the "not wasting cycles" is over-engineering. It might be a valid assertion if you're serializing a million entities per second. Otherwise the JVM will optimize the "hot spot" for you. And anyway, that won't be the bottleneck in your application architecture.
If you know your entities have a "children" array field in common, you might want to apply the same JsonSerializer
to all of them, by simply maintining a Map
of the compatible classes.
You have to understand that Jackson has its own limitations. If you need something more than that, you might want a totally custom solution. This is the best you can obtain with Jackson.
Hope the answer is satisfactory.
You can use a custom JsonSerializer<T>
.
class EntitySerializer extends StdSerializer<Entity> {
private static final long serialVersionUID = 1L;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
EntitySerializer() {
super(Entity.class);
}
@Override
public void serialize(
final Entity value,
final JsonGenerator generator,
final SerializerProvider provider) throws IOException {
final TreeNode jsonNode = OBJECT_MAPPER.valueToTree(value);
if (!AuthUtils.allowed("ChildView")) {
final TreeNode children = jsonNode.get("children");
if (children.isArray()) {
((ContainerNode<ArrayNode>) children).removeAll();
}
}
generator.writeTree(jsonNode);
}
}
However, as you can see we are using an ObjectMapper
instance inside our JsonSerializer
(or would you prefer manually "writing" each field with JsonGenerator
? I don't think so :P). Since ObjectMapper
looks for annotations, to avoid infinite recursion of the serialization process, you have to ditch the class annotation
@JsonSerialize(using = EntitySerializer.class)
And register the custom JsonSerializer
manually to the Jackson ObjectMapper
.
final SimpleModule module = new SimpleModule();
module.setSerializerModifier(new BeanSerializerModifier() {
@Override
public JsonSerializer<?> modifySerializer(
final SerializationConfig config,
final BeanDescription beanDesc,
final JsonSerializer<?> serializer) {
final Class<?> beanClass = beanDesc.getBeanClass();
return beanClass == Entity.class ? new EntitySerializer() : serializer;
}
});
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Finally, you just have to use the ObjectMapper
, or let your framework use it.
As you're using Spring, you can register a @Bean
of type ObjectMapper, marked as @Primary
, or you can register a @Bean
of type Jackson2ObjectMapperBuilder
.
Previous answer.
As the allowed
method is static, that means it can be accessed from "everywhere".
After fiddling a little bit with Jackson, I'll give you the first of the two options, as I'm still working on the second one.
Annotate your class with
@JsonSerialize(converter = EntityConverter.class)
public class Entity { ... }
Here you're specifying a custom Converter
.
The Converter
implementation is pretty neat.
Inside the static block I'm simply getting the Auth
annotation value, but that is optional, you can do what you feel like is best for your usecase.
class EntityConverter extends StdConverter<Entity, Entity> {
private static final String AUTH_VALUE;
static {
final String value;
try {
final Field children = Entity.class.getDeclaredField("children");
final AuthSerialize auth = children.getAnnotation(AuthSerialize.class);
value = auth != null ? auth.value() : null;
} catch (final NoSuchFieldException e) {
// Provide appropriate Exception, or handle it
throw new RuntimeException(e);
}
AUTH_VALUE = value;
}
@Override
public Entity convert(final Entity value) {
if (AUTH_VALUE != null) {
if (!AuthUtils.allowed(AUTH_VALUE)) {
value.children.clear();
}
}
return value;
}
}
Let me know if this is sufficient, or you'd prefer a more complex solution.