Jackson renomme le champ booléen primitif en supprimant «est»

93

Cela pourrait être un double. Mais je ne trouve pas de solution à mon problème.

j'ai un cours

public class MyResponse implements Serializable {

    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
}

Les getters et setters sont générés par Eclipse.

Dans une autre classe, j'ai défini la valeur sur true et je l'écris sous forme de chaîne JSON.

System.out.println(new ObjectMapper().writeValueAsString(myResponse));

Dans JSON, la clé arrive en tant que {"success": true}.

Je veux la clé comme isSuccesselle-même. Jackson utilise-t-il la méthode setter lors de la sérialisation? Comment faire de la clé le nom du champ lui-même?

iCode
la source
1
si votre nom de propriété est liek isSuccess, le nom de votre méthode doit être isIsSuccessje pense
Jens
Je comprends. J'ai pensé que c'était mieux SetSuccess car il est généré par Eclipse. (Suivant une norme)
iCode

Réponses:

119

C'est une réponse un peu tardive, mais peut être utile pour quiconque accède à cette page.

Une solution simple pour changer le nom que Jackson utilisera lors de la sérialisation en JSON consiste à utiliser l' annotation @JsonProperty , votre exemple deviendrait donc:

public class MyResponse implements Serializable {

    private boolean isSuccess;

    @JsonProperty(value="isSuccess")        
    public boolean isSuccess() {
        return isSuccess;
    }

    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
}

Cela serait alors sérialisé en JSON en tant que {"isSuccess":true}, mais présente l'avantage de ne pas avoir à modifier le nom de votre méthode getter.

Notez que dans ce cas, vous pouvez également écrire l'annotation @JsonProperty("isSuccess")car elle n'a qu'un seul valueélément

Scott
la source
Cette méthode ne fonctionnera pas pour mon cas car la classe ne m'appartient pas car elle provient de dépendances tierces. Pour un tel cas, voir ma réponse ci-dessous.
edmundpie
4
Im en utilisant boot printemps avec jackson mais obtenir deux champs est un « succès » et autre « isSuccess » et quand j'utilise booléenne non primitive que seul champ « isSuccess »
Vishal Singla
@VishalSingla J'ai exactement le même problème, cette solution produit deux champs dans Spring Boot
Aron Fiechter
22

J'ai récemment rencontré ce problème et c'est ce que j'ai trouvé. Jackson inspectera toute classe que vous lui passerez pour les getters et les setters, et utilisera ces méthodes pour la sérialisation et la désérialisation. Ce qui suit "get", "is" et "set" dans ces méthodes sera utilisé comme clé pour le champ JSON ("isValid" pour getIsValid et setIsValid).

public class JacksonExample {   

    private boolean isValid = false;

    public boolean getIsValid() {
        return isValid;
    }

    public void setIsValid(boolean isValid) {
        this.isValid = isValid;
    }
} 

De même, "isSuccess" deviendra "success", sauf s'il est renommé "isIsSuccess" ou "getIsSuccess"

En savoir plus ici: http://www.citrine.io/blog/2015/5/20/jackson-json-processor

Utkarsha Padhye
la source
6
isValid n'est pas la bonne convention de dénomination pour le type de données booléen en java. doit être valide et isValid (), setValid ()
vels4j
2
mais n'est-ce pas censé être exactement cela? Une convention? S'il existe, pourriez-vous créer un lien vers la référence Jackson qui indique qu'il utilise des noms de lecture comme champs JSON? Ou pensez-vous que ce soit un mauvais choix de conception?
Abhinav Vishak
2
J'aimerais qu'il y ait un avertissement à ce sujet
RyPope
@ vels4j Les conventions de dénomination disparaissent lorsque vous avez affaire à des implémentations très spécifiques.
Dragas
13

L'utilisation des deux annotations ci-dessous force le JSON de sortie à inclure is_xxx:

@get:JsonProperty("is_something")
@param:JsonProperty("is_something")
Fabio
la source
C'est la meilleure réponse à cette question.
dustinevan
1
Est-ce Java? C'est peut-être Kotlin?
spottedmahn
5

Vous pouvez configurer votre ObjectMappercomme suit:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
            @Override
            public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
            {
                if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
                        && method.getName().startsWith("is")) {
                    return method.getName();
                }
                return super.nameForGetterMethod(config, method, defaultName);
            }
        });
burak emre
la source
1
J'aime que vous essayiez de résoudre ce problème via la configuration. Cependant, cela ne fonctionnera que si vous préfixez toujours vos champs booléens et vos propriétés JSON avec "est". Supposons que vous ayez un autre champ booléen simplement nommé «activé» que vous souhaitez sérialiser en tant que tel. Étant donné que la méthode générée est «isEnabled ()», le code ci-dessus la sérialisera ensuite en «isEnabled» au lieu de simplement «enabled». Finalement, le problème est que pour les deux champs «x» et «isX», Eclipse génère la méthode «isX ()»; vous ne pouvez donc pas déduire un nom de propriété correspondant au champ.
David Siegal
@DavidSiegal base sur la réponse de burak J'ai étendu la réponse ci-dessous pour soutenir un tel cas.
edmundpie
4

Lorsque vous utilisez Kotlin et des classes de données:

data class Dto(
    @get:JsonProperty("isSuccess") val isSuccess: Boolean
)

Vous devrez peut-être ajouter @param:JsonProperty("isSuccess")si vous envisagez également de désérialiser JSON.

Eirik Fosse
la source
2

S'appuyant sur la réponse d'Utkarsh.

Les noms Getter moins get / is sont utilisés comme nom JSON.

public class Example{
    private String radcliffe; 

    public getHarryPotter(){
        return radcliffe; 
    }
}

est stocké sous le nom {"harryPotter": "whatYouGaveHere"}


Pour la désérialisation, Jackson vérifie à la fois le setter et le nom du champ. Pour la chaîne Json {"word1": "example"} , les deux ci-dessous sont valides.

public class Example{
    private String word1; 

    public setword2( String pqr){
        this.word1 = pqr; 
    }
}

public class Example2{
    private String word2; 

    public setWord1(String pqr){
        this.word2 = pqr ; 
    }
}

Une question plus intéressante est celle de l'ordre que Jackson considère pour la désérialisation. Si j'essaye de désérialiser {"word1": "myName"} avec

public class Example3{
    private String word1;
    private String word2; 

    public setWord1( String parameter){
        this.word2 = parameter ; 
    }
}

Je n'ai pas testé le cas ci-dessus, mais il serait intéressant de voir les valeurs de word1 & word2 ...

Remarque: j'ai utilisé des noms radicalement différents pour souligner quels champs doivent être identiques.

Abhinav Vishak
la source
1

il existe une autre méthode pour résoudre ce problème.

il suffit de définir une nouvelle sous-classe étend PropertyNamingStrategy et de la transmettre à l'instance ObjectMapper.

voici un extrait de code qui peut être plus utile:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
        @Override
        public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
            String input = defaultName;
            if(method.getName().startsWith("is")){
                input = method.getName();
            }

            //copy from LowerCaseWithUnderscoresStrategy
            if (input == null) return input; // garbage in, garbage out
            int length = input.length();
            StringBuilder result = new StringBuilder(length * 2);
            int resultLength = 0;
            boolean wasPrevTranslated = false;
            for (int i = 0; i < length; i++)
            {
                char c = input.charAt(i);
                if (i > 0 || c != '_') // skip first starting underscore
                {
                    if (Character.isUpperCase(c))
                    {
                        if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
                        {
                            result.append('_');
                            resultLength++;
                        }
                        c = Character.toLowerCase(c);
                        wasPrevTranslated = true;
                    }
                    else
                    {
                        wasPrevTranslated = false;
                    }
                    result.append(c);
                    resultLength++;
                }
            }
            return resultLength > 0 ? result.toString() : input;
        }
    });
Eric
la source
1

Je ne voulais pas jouer avec certaines stratégies de dénomination personnalisées, ni recréer certains accesseurs.
Moins il y a de code, plus je suis heureux.

Cela a fait l'affaire pour nous:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties({"success", "deleted"}) // <- Prevents serialization duplicates 
public class MyResponse {

    private String id;
    private @JsonProperty("isSuccess") boolean isSuccess; // <- Forces field name
    private @JsonProperty("isDeleted") boolean isDeleted;

}
Adrien
la source
1

La réponse acceptée ne fonctionnera pas pour mon cas.

Dans mon cas, la classe ne m'appartient pas. La classe problématique provient de dépendances tierces, je ne peux donc pas simplement y ajouter une @JsonPropertyannotation.

Pour le résoudre, inspiré de la réponse @burak ci-dessus, j'ai créé une personnalisation PropertyNamingStrategycomme suit:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
  @Override
  public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
  {
    if (method.getParameterCount() == 1 &&
            (method.getRawParameterType(0) == Boolean.class || method.getRawParameterType(0) == boolean.class) &&
            method.getName().startsWith("set")) {

      Class<?> containingClass = method.getDeclaringClass();
      String potentialFieldName = "is" + method.getName().substring(3);

      try {
        containingClass.getDeclaredField(potentialFieldName);
        return potentialFieldName;
      } catch (NoSuchFieldException e) {
        // do nothing and fall through
      }
    }

    return super.nameForSetterMethod(config, method, defaultName);
  }

  @Override
  public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
  {
    if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
        && method.getName().startsWith("is")) {

      Class<?> containingClass = method.getDeclaringClass();
      String potentialFieldName = method.getName();

      try {
        containingClass.getDeclaredField(potentialFieldName);
        return potentialFieldName;
      } catch (NoSuchFieldException e) {
        // do nothing and fall through
      }
    }
    return super.nameForGetterMethod(config, method, defaultName);
  }
});

Fondamentalement, cela fait, avant de sérialiser et de désérialiser, il vérifie dans la classe cible / source quel nom de propriété est présent dans la classe, que ce soit isEnabledou une enabledpropriété.

Sur cette base, le mappeur sérialisera et désérialisera le nom de propriété existant.

Edmundpie
la source
0

Vous pouvez changer le booléen primitif en java.lang.Boolean (+ utiliser @JsonPropery)

@JsonProperty("isA")
private Boolean isA = false;

public Boolean getA() {
    return this.isA;
}

public void setA(Boolean a) {
    this.isA = a;
}

A parfaitement fonctionné pour moi.

Reynard
la source