On Tue, Aug 8, 2017 at 4:03 PM, Matteo wrote:
> Hello!
>
> I'm trying to write a custom serializer that can transform the following
> structure
>
> {
> "a": {
> "@context": "context-a",
> "aKey": "aValue"
> },
> "b": {
> "@context": "context-b",
> "anotherbKey": "anotherbValue",
> "bKey": "bValue"
> }
> }
>
> into this:
>
> {
> "@context" : ["context-a", "context-b"],
> "a": {
> "aKey": "aValue"
> },
> "b": {
> "anotherbKey": "anotherbValue",
> "bKey": "bValue"
> }
> }
>
> The reason to do this is to put all json-ld headers at the beginning of the
> serialized json. I have a utility class that extract all @context attribute
> from a bean hierarchy (ContextsCrawler in the snippet below) and my current
> serializer attempt is this:
>
> public class JsonLdModelSerializer extends JsonSerializer {
>
> private static Optional baseContext;
> private static ContextsCrawler ctxCrawler = new ContextsCrawler();
>
> public static void scanClassForContexts(Map mixins)
> {
> ctxCrawler.scanClassForContexts(mixins);
> }
>
> public static void setBaseContext(String baseContext) {
> JsonLdModelSerializer.baseContext = Optional.of(baseContext);
> }
>
> @Override
> public void serialize(Object value, JsonGenerator jgen,
> SerializerProvider serializers)
> throws IOException, JsonProcessingException {
>
> jgen.writeStartObject();
> // Add contexts elements in top level element only:
> if (jgen.getOutputContext().getParent().inRoot()) {
> Collection cxts = ctxCrawler.getContexts(value);
> if (cxts != null) {
> Set ctxset = new HashSet<>();
> baseContext.ifPresent(ctx -> ctxset.add(ctx));
> ctxset.addAll(cxts);
> jgen.writeObjectField("@context", ctxset);
> }
> }
> JavaType javaType = serializers.constructType(value.getClass());
> BeanDescription beanDesc =
> serializers.getConfig().introspect(javaType);
> JsonSerializer serializer =
> BeanSerializerFactory.instance.findBeanSerializer(serializers,
> javaType,
> beanDesc);
> serializer.unwrappingSerializer(null).serialize(value, jgen,
> serializers);
>
> jgen.writeEndObject();
> }
> }
>
>
> Then I simply define a mixin for each class that contains a @context element
> like this:
>
> @JsonSerialize(using = JsonLdModelSerializer.class)
> @JsonIgnoreProperties("context")
> public class CapabilityMixIn extends Capability {
> @Override
> @JsonLdContextProvider // used by ContextsCrawler
> public List getContext() {
> return super.getContext();
> }
> }
>
> It works but it has two main problems:
>
> 1. I don't know it the direct usage of BeanSerializerFactory in serialize
> method is OK since I read that this is not the best approach to use. I'm
> also worried about performance implications;
> 2. For some reason this serializer doesn't play nice with beans that
> contains Map that are decorated with @JsonAnyGetter: the
> resulting json does not contain the map elements
>
> Could you please provide guidance? What is the right approach to implement
> such a JsonLdModelSerializer?
Ok, that is quite an elaborate setup.
So... typically I recommend multi-phase processing for most structural
changes: first "serialize" content into `JsonNode` using
`ObjectMapper.valueToTree()`; then transform tree, and finally
serialize the modified tree as JSON.
This is especially true for cases where you need to apply
transformations for all kinds of types, not just specific classes.
Serializer/deserializer setup is designed more for strict(er) typing,
and as such is not necessarily good at applying things for all types.
But it is certainly possible to do this via serializers too. Usually I
would have looked at `StdDelegatingSerializer`, in which you can take
a specific type (that Jackson does not know how to properly
serialize), and construct alternative value (like `JsonNode` or `Map`)
and let Jackson serialize that instead. This is somewhat similar to
use of `@JsonValue` annotation, in which POJO is serialized using sort
of surrogate.
But I am not sure this approach would work here, since this is applied
to any types it seems (`Object`), and since you may want to operate on
outputs of standard Bean-style serialization.
As to use of `BeanSerializerFactory`: you are correct in suspecting
this is not the way to do it. It is not. :-)
Instead you would ideally do one of:
1. If types are statically known, implement `ContextualSerializer`, in
which you can get access to the "standard" serializer, keep reference
to it, add your own implementation that may call original one
(including giving it `TokenBuffer` as `JsonGenerator`!).
2. If types not known until actual serialization