2016-08-26 1 views
1

J'ai un Range que j'ai besoin de sérialiser dans un certain format, à savoir "[lower,upper)". Pour ce faire, je l'ai écrit un sérialiseur de base:Sérialiser Range avec Jackson

public class RangeSerializer extends StdSerializer<Range<?>> { 

    @Override 
    public void serialize(final Range<?> value, final JsonGenerator gen, final SerializerProvider provider) throws IOException { 
    if (value != null) { 
     gen.writeRaw('"'); 
     gen.writeRaw('['); 
     provider.defaultSerializeValue(value.lowerEndpoint(), gen); 
     gen.writeRaw(','); 
     provider.defaultSerializeValue(value.upperEndpoint(), gen); 
     gen.writeRaw(')'); 
     gen.writeRaw('"'); 
    } 
    } 

(Notez que, en réalité, la sérialisation gère les différentes possibilités de Range telles que les plages fermées/ouvertes, la possibilité de plages sans bornes à l'une des extrémités, etc., mais pour les fins de ma question qui ne sont pas pertinentes, donc je l'ai supprimé pour garder le code simple). Mon problème est qu'en revenant au sérialiseur par défaut pour chaque classe je me retrouve avec des citations au mauvais endroit. Par exemple, si j'avais un Range<String> avec une entrée "[foo,bar)" puis lors de la sérialisation, j'obtiens "["foo","bar")". J'ai besoin du résultat sans guillemets autour des valeurs de point final inférieur et supérieur.

Je comprends que les citations supplémentaires sont parce que gen.writeString() dans le sérialiseur sous-jacent ne réalise pas qu'il est déjà dans une chaîne. Y a-t-il un moyen de le faire savoir au générateur ou un autre moyen de réaliser ce que je tente de faire?

Notez que Range<?> est vraiment générique, donc je ne peux pas coder en dur la sérialisation des valeurs. Il doit fonctionner pour Range<Integer>, Range<String>, Range<DateTime> et toute autre chose.

Répondre

0

Je n'ai pas pu obtenir votre approche d'utiliser un seul générateur pour sérialiser tout ce qui fonctionne. Vous pourriez être en mesure d'utiliser un DelegatingJsonGenerator et de décrocher certains appels, mais j'ai décidé que la manière montrée ci-dessous était nettement plus simple (au prix d'une perte de performance mineure).

Voici une classe de test Spock démontrant comment j'ai réussi à faire fonctionner ça.

@Grab('com.fasterxml.jackson.core:jackson-databind:2.8.1') 
@Grab('org.spockframework:spock-core:1.0-groovy-2.4') 

import com.fasterxml.jackson.databind.ObjectMapper 
import com.fasterxml.jackson.databind.SerializationFeature 
import com.fasterxml.jackson.databind.annotation.JsonSerialize 
import com.fasterxml.jackson.databind.util.StdConverter 
import spock.lang.Specification 
import spock.lang.Unroll 

class RangeSerializationTest extends Specification { 

    static class Range<T> { 
     T lower 
     T upper 
    } 

    @JsonSerialize(converter = RangeConverter) 
    static interface RangeMixin { 
    } 

    static class RangeConverter extends StdConverter<Range, String> { 
     private static final mapper = new ObjectMapper().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 

     @Override 
     String convert(Range value) { 
      def lower = mapper.convertValue(value.lower, String) 
      def upper = mapper.convertValue(value.upper, String) 
      return "[$lower,$upper)" 
     } 
    } 


    @Unroll 
    def 'range of #lower.class'() { 
     given: 
     def mapper = new ObjectMapper() 
     mapper.addMixIn(Range, RangeMixin) 

     expect: 
     mapper.writeValueAsString(new Range(lower: lower, upper: upper)) == expectedJson 

     where: 
     lower   | upper   | expectedJson 
     'abc'   | 'def'   | '"[abc,def)"' 
     123   | 456   | '"[123,456)"' 
     new Date(123) | new Date(456) | '"[1970-01-01T00:00:00.123+0000,1970-01-01T00:00:00.456+0000)"' 
    } 
} 
+0

Merci pour la réponse. Un convertisseur est une façon intéressante de faire les choses, mais peut-il être enregistré de la même manière que les sérialiseurs et les désérialiseurs peuvent être enregistrés ou doit-il être attaché avec une annotation? Je n'ai pas accès à la classe Range elle-même, et la plage est en fait utilisée comme clé pour une carte, donc je ne suis pas sûr qu'une solution basée sur des annotations puisse m'aider ici. – jgm

+0

Utilisez un mixage pour ajouter l'annotation à la classe Range. J'ai édité ma réponse pour montrer comment. Notez la partie 'addMixin (...)' configurée sur le mappeur. –