Progressive Deserialization

61 views
Skip to first unread message

Hob Spillane

unread,
Sep 20, 2016, 2:07:33 PM9/20/16
to jackson-user
I'm trying to do something a bit non-standard and hoping someone has a suggestion.  I'd like to use elements at the top of my JSON document to build up objects further down the document.  Here's a contrived example:
{
 
"templates": {
   
"quadruped": {
     
"legs": 4
   
},
   
"exotherm": {
     
"isColdBlooded": true
   
},
   
"endotherm": {
     
"isColdBlooded": false
   
}
 
},
 
"animals": {
   
"aligator": {
     
"exterior": "scales",
     
"templates": [
       
"quadruped",
       
"exotherm"
     
]
   
},
   
"cow": {
     
"exterior": "fur",
     
"templates": [
       
"quadruped",
       
"endotherm"
     
]
   
}
 
}
}

What I'm hoping to wind up with is deserialized Aligator & Cow instances that have the properties from their templates merged in.  I've played around a bit with customer deserializers and contextual deserializers but I'm having trouble finding a way to make the templates available to the deserializers for Cow & Aligator.

Any suggestions would be greatly appreciated.  Thanks in advance.

-Hob

luis.esc...@gmail.com

unread,
Sep 27, 2016, 3:02:35 PM9/27/16
to jackson-user
I'm facing the exact same problem, any help would be much appreciated.

Tatu Saloranta

unread,
Sep 27, 2016, 3:26:59 PM9/27/16
to jackso...@googlegroups.com
I am not completely sure I understand the goal, but in general serializers and deserializers only have access to:

1. Current position in input/output stream (current token for deserializers)
2. Nesting information (parent property) via `JsonParser`, for inclusion
3. Explicitly assigned contextual properties: (de)serializers can set/get attributes via `DeserializationContext` and `SerializerProvider`

What deserializers do NOT have access to is the rest of the input document: no copy is kept in memory, except by deserializers that explicitly buffer said content (sometimes needed for polymorphic handling and creator methods).

So: if you need to access different parts of the document, it is probably necessary to read the whole document in (as JsonNode, for example), and modify it as a separate operation. Jackson design is not geared towards significant structural changes, so templating (for example) is out of scope.

-+ Tatu +-
 

-Hob

--
You received this message because you are subscribed to the Google Groups "jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user+unsubscribe@googlegroups.com.
To post to this group, send email to jackso...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Hob Spillane

unread,
Oct 8, 2016, 2:16:25 PM10/8/16
to jackson-user
Tatu,

Thanks for the reply.  DeserializationContext was really the piece I was missing.  I was able to get it working with just 1 smallish hack.  I'll lay out what I did here for Luis, and anyone else that might be looking for something similar, with some pseudo code for the Animal example in my original post.  Feel free to let me know if there are better ways to do what I've done.  

1.  Added my own DeserializerModifier and used it to inject decorators for the built-in deserializers
public class TemplateDeserializerModifer {

    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        final JsonDeserializer<?> modified = super.modifyDeserializer(config, beanDesc, deserializer);
        if(Template.class.isAssignableFrom(beanDesc.getBeanClass())) {
            return new TemplatesDeserializer((JsonDeserializer<Templates>) modified);
        }else if(Animal.class.isAssignableFrom(beanDesc.getBeanClass()) && modified instanceof BeanDeserializer) {
            return new AnimalDeserializer((BeanDeserializer) modified);
        }else{
            return modified;
        }
    }
}

2. Attach my templates to the DeserializationContext in TemplatesDeserializer
public class TemplatesDeserializer extends StdDeserializer<Templates> implements ResolvableDeserializer {

    
public static final String TEMPLATES_KEY_NAME = "templates";
   
private final JsonDeserializer<Templates> delegate;

    
protected TemplatesDeserializer(JsonDeserializer<Templates> delegate) {
       
super(Templates.class);
       
this.delegate = delegate;
   
}

   
@Override
   
public Templates deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
       
Templates templates = delegate.deserialize(p, ctxt);
        ctxt
.setAttribute(TEMPLATES_KEY_NAME, templates);
       
return templates;
   
}


   
@Override
   
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
       
if(delegate instanceof ResolvableDeserializer) {
           
((ResolvableDeserializer) delegate).resolve(ctxt);
       
}
   
}
}

3. In AnimalDeserializer, leverage the templates now available through the DeserializationContext
public class AnimalDeserializer extends BeanDeserializer implements ResolvableDeserializer {
 
 
public AnimalDeserializer(BeanDeserializer originalDeserializer) {
       
super(originalDeserializer);
   
}


   
@Override
   
public Animal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
       
Animal animal = (Animal) super.deserialize(p, ctxt);
       
Templates templates = (Templates) ctxt.getAttribute(TemplatesDeserializer.TEMPLATES_KEY_NAME);
       
Template template = getTemplate(animal, templates);
       
for(Map.Entry<String, String> entry : template.getProperties() {
           
String propName = entry.getKey();
           
String propValue = entry.getValue();
           
SettableBeanProperty prop = _beanProperties.find(propName);
           
if (prop != null) { // normal case
               
try {
                    prop
.deserializeAndSet(new TemplateParser(propValue), ctxt, tag);
               
} catch (Exception e) {
                    wrapAndThrow
(e, tag, propName, ctxt);
               
}
           
}
       
}
       
return tag;
   
}
}

The hack I referred to earlier is there where I create an instance of TemplateParser.  Template parser is a new sub-class of JsonParser that exists solely to provide the data necessary for BeanDeserializer to parser a few more properties from somewhere other than the parser it's already got.  Most of the necessary method implementations in that new class are NO-OP.  The only ones that matter looks about like this:
public class TemplateParser extends JsonParser {


   
private final String template;


   
TemplateParser(String template) {
       
this.template = template;
   
}


   
@Override
   
public JsonToken getCurrentToken() {
       
return JsonToken.VALUE_STRING;
   
}


   
@Override
   
public String getText() throws IOException {
       
return template;
   
}
}

Hopefully all that's not too big a hack.  It does exactly what I was hoping for.

Thanks,
Hob

Tatu Saloranta

unread,
Oct 10, 2016, 3:34:05 PM10/10/16
to jackso...@googlegroups.com
Sounds good. The only possible further improvement I can think of would be use of `JsonParserDelegate` which could be useful if you need to dispatch most calls to an existing real `JsonParser`.

-+ Tatu +-


--

Hob Spillane

unread,
Oct 11, 2016, 8:53:50 AM10/11/16
to jackson-user
Great tip.  Thanks again, Tatu.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages