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.


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.


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:
}

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"}

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"}
    }
}

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");
    }
}

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.