Re: [jackson-user] Jackson custom serializers

2017-08-10 Thread Matteo
Dear Tatu,
I really thank you for your immediate and very thorough answer, I really 
appreciate!

I tried with the SerializerProvider approach but I was not able to 
implement something that works. Could you please point me to the right 
direction with the approach no.(2)? Is there any simple example I can start 
with in the jackson codebase?

Thank you!

matteo

On Wednesday, August 9, 2017 at 9:44:59 PM UTC+2, Tatu Saloranta wrote:
>
> 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 

Re: [jackson-user] Jackson custom serializers

2017-08-09 Thread Tatu Saloranta
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