Sérialisation personnalisée Jackson JSON pour certains champs

93

Existe-t-il un moyen d'utiliser le processeur Jackson JSON pour effectuer une sérialisation au niveau du champ personnalisé? Par exemple, j'aimerais avoir la classe

public class Person {
    public String name;
    public int age;
    public int favoriteNumber;
}

sérialisé au JSON suivant:

{ "name": "Joe", "age": 25, "favoriteNumber": "123" }

Notez que age = 25 est codé comme un nombre tandis que favoriteNumber = 123 est codé comme une chaîne . Out of the box Jackson marshalls intà un certain nombre. Dans ce cas, je veux que favoriteNumber soit encodé sous forme de chaîne.

Steve Kuo
la source
1
J'ai écrit un article sur Comment écrire un sérialiseur personnalisé avec Jackson qui peut être utile à certains.
Sam Berry

Réponses:

106

Vous pouvez implémenter un sérialiseur personnalisé comme suit:

public class Person {
    public String name;
    public int age;
    @JsonSerialize(using = IntToStringSerializer.class, as=String.class)
    public int favoriteNumber:
}


public class IntToStringSerializer extends JsonSerializer<Integer> {

    @Override
    public void serialize(Integer tmpInt, 
                          JsonGenerator jsonGenerator, 
                          SerializerProvider serializerProvider) 
                          throws IOException, JsonProcessingException {
        jsonGenerator.writeObject(tmpInt.toString());
    }
}

Java doit gérer l'autoboxing de intà Integerpour vous.

Kevin Bowersox
la source
3
Jackson-databind (au moins 2.1.3) contient déjà un ToStringSerializer spécial, voir ma réponse.
werupokz
@KevinBowersox Pouvez-vous m'aider avec mon problème de désérialisation s'il vous plaît?
JJD
Y a-t-il un moyen moins terrible de faire cela? Comme Person implements ToJson?
jameshfisher
1
Dans mon cas, cela a même échoué sur la as=String.classpièce, en raison des types que j'ai utilisés. @ kevin-bowersox, je suggère de mettre à jour votre commentaire, conformément à ce que @GarethLatty a dit.
Bert
54

Jackson-databind (au moins 2.1.3) fournit special ToStringSerializer( com.fasterxml.jackson.databind.ser.std.ToStringSerializer)

Exemple:

public class Person {
    public String name;
    public int age;
    @JsonSerialize(using = ToStringSerializer.class)
    public int favoriteNumber:
}
werupokz
la source
3
Qu'en est-il de l'inverse où une chaîne doit être convertie en un entier? Je ne vois pas ToIntSerializer.class.
jEremyB
@jEremyB Vous devrez peut-être écrire un désérialiseur personnalisé
Drew Stephens
ToStringSerializer fonctionne mais FloatSerializer apporte ce message: Impossible d'écrire le contenu: java.lang.Integer ne peut pas être
converti en
12

jackson-annotations fournit @JsonFormatqui peut gérer de nombreuses personnalisations sans avoir besoin d'écrire le sérialiseur personnalisé.

Par exemple, la demande d'une STRINGforme pour un champ de type numérique affichera la valeur numérique sous forme de chaîne

public class Person {
    public String name;
    public int age;
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    public int favoriteNumber;
}

se traduira par la sortie souhaitée

{"name":"Joe","age":25,"favoriteNumber":"123"}
Oleg Estekhin
la source
11

Ajoutez un @JsonPropertygetter annoté, qui renvoie a String, pour le favoriteNumberchamp:

public class Person {
    public String name;
    public int age;
    private int favoriteNumber;

    public Person(String name, int age, int favoriteNumber) {
        this.name = name;
        this.age = age;
        this.favoriteNumber = favoriteNumber;
    }

    @JsonProperty
    public String getFavoriteNumber() {
        return String.valueOf(favoriteNumber);
    }

    public static void main(String... args) throws Exception {
        Person p = new Person("Joe", 25, 123);
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(p)); 
        // {"name":"Joe","age":25,"favoriteNumber":"123"}
    }
}
João Silva
la source
7

Si vous ne souhaitez pas polluer votre modèle avec des annotations et que vous souhaitez effectuer des opérations personnalisées, vous pouvez utiliser des mixins.

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Person.class, PersonMixin.class);
mapper.registerModule(simpleModule);

Remplacer l'âge:

public abstract class PersonMixin {
    @JsonSerialize(using = PersonAgeSerializer.class)
    public String age;
}

Faites tout ce dont vous avez besoin avec l'âge:

public class PersonAgeSerializer extends JsonSerializer<Integer> {
    @Override
    public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(String.valueOf(integer * 52) + " months");
    }
}
Igor G.
la source
2

avec l'aide de @JsonView, nous pouvons décider des champs de classes de modèle à sérialiser qui satisfont les critères minimaux (nous devons définir les critères) comme nous pouvons avoir une classe de base avec 10 propriétés mais seulement 5 propriétés peuvent être sérialisées, ce qui est nécessaire pour le client seulement

Définissez nos vues en créant simplement la classe suivante:

public class Views
{
    static class Android{};
    static class IOS{};
    static class Web{};
}

Classe de modèle annotée avec vues:

public class Demo 
{
    public Demo() 
    {
    }

@JsonView(Views.IOS.class)
private String iosField;

@JsonView(Views.Android.class)
private String androidField;

@JsonView(Views.Web.class)
private String webField;

 // getters/setters
...
..
}

Nous devons maintenant écrire un convertisseur json personnalisé en étendant simplement la classe HttpMessageConverter à partir de spring comme:

    public class CustomJacksonConverter implements HttpMessageConverter<Object> 
    {
    public CustomJacksonConverter() 
        {
            super();
        //this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.ClientView.class));
        this.delegate.getObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        this.delegate.getObjectMapper().setSerializationInclusion(Include.NON_NULL);

    }

    // a real message converter that will respond to methods and do the actual work
    private MappingJackson2HttpMessageConverter delegate = new MappingJackson2HttpMessageConverter();

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return delegate.canRead(clazz, mediaType);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return delegate.canWrite(clazz, mediaType);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return delegate.getSupportedMediaTypes();
    }

    @Override
    public Object read(Class<? extends Object> clazz,
            HttpInputMessage inputMessage) throws IOException,
            HttpMessageNotReadableException {
        return delegate.read(clazz, inputMessage);
    }

    @Override
    public void write(Object obj, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException 
    {
        synchronized(this) 
        {
            String userAgent = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("userAgent");
            if ( userAgent != null ) 
            {
                switch (userAgent) 
                {
                case "IOS" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.IOS.class));
                    break;
                case "Android" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.Android.class));
                    break;
                case "Web" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( Views.Web.class));
                    break;
                default:
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
                    break;
                }
            }
            else
            {
                // reset to default view
                this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
            }
            delegate.write(obj, contentType, outputMessage);
        }
    }

}

Maintenant, il est nécessaire de dire à spring d'utiliser cette conversion json personnalisée en la plaçant simplement dans dispatcher-servlet.xml

<mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean id="jsonConverter" class="com.mactores.org.CustomJacksonConverter" >
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

C'est ainsi que vous pourrez décider des champs à sérialiser.

Chetan Pardeshi
la source