J'utilise Spring 4 MVC avec Jackson 2 pour mon service. Pour l'une des opérations, j'ai un objet de requête qui a un attribut dans lequel le premier cas de chameau, Word, ne compte qu'une lettre:
private String aLogId;
Cette classe comprend les accesseurs et les setters nommés de manière appropriée:
public String getALogId() { return aLogId; }
public void setALogId(String aLogId) { this.aLogId = aLogId; }
Cependant, lorsque je tente de publier une demande sur ce service à l'aide de la propriété JSON correspondante:
{"aLogId":"This is a log id"}
Je reçois une réponse 500 du framework Spring indiquant que le champ n'est pas reconnu et que ma classe de contrôleurs n'est jamais appelée:
Impossible de lire JSON: Le champ non reconnu "aLogId" (class
Cependant, lorsque je modifie le "L" en minuscule, la demande est désérialisée comme prévu et ma classe de contrôleurs est touchée:
{"alogId":"This is a log id"}
Pourquoi Jackson s’attend-il à ce que le «L» soit en minuscule, alors qu’il s’agit évidemment du deuxième mot de la convention de casse-chameau de l’attribut et destiné à être en majuscule? Est-ce parce que le premier mot est seulement une lettre longue?
Il y a d'autres attributs dans l'objet de requête où le premier mot est composé de plus d'une lettre et ceux qui sont attribués ne sont pas confrontés au même problème d'incompatibilité.
Le problème que vous voyez est dû au fait que Jackson utilise les conventions de dénomination Java Bean pour déterminer les propriétés Json dans une classe Java.
Voici un référence du problème spécifique que vous voyez, il est recommandé de ne pas mettre en majuscule les deux premières lettres de votre champ. Si vous utilisez un IDE comme IntelliJ ou Eclipse et laissez le IDE générer les paramètres pour vous, vous remarquerez que le même "comportement" se produit, vous obtiendrez les méthodes suivantes:
public void setaLogId(String aLogId) {
this.aLogId = aLogId;
}
public String getaLogId() {
return aLogId;
}
Par conséquent, lorsque vous modifiez le "L" en minuscule, Jackson est en mesure de déterminer le champ que vous souhaitez mapper.
Cela dit, vous avez toujours la possibilité d'utiliser le nom de champ "aLogId" et de faire en sorte que Jackson fonctionne, vous n'avez plus qu'à utiliser l'annotation @JsonProperty
contenant la variable aLogId
.
@JsonProperty("aLogId")
private String aLogId;
Le code de test suivant montre comment cela fonctionnera:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test {
@JsonProperty("aLogId")
private String aLogId;
public void setaLogId(String aLogId) {
this.aLogId = aLogId;
}
public String getaLogId() {
return aLogId;
}
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
Test test = new Test();
test.setaLogId("anId");
try {
System.out.println("Serialization test: " + objectMapper.writeValueAsString(test));
String json = "{\"aLogId\":\"anotherId\"}";
Test anotherTest = objectMapper.readValue(json, Test.class);
System.out.println("Deserialization test: " +anotherTest.getaLogId());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Le résultat du test est:
Serialization test: {"aLogId":"anId"}
Deserialization test: anotherId
Je crois comprendre que Jackson utilise par défaut sa propre convention de dénomination, qui est très proche, mais pas exactement identique, de la convention de dénomination Java Bean. Une option MapperFeature, MapperFeature.USE_STD_BEAN_NAMING, a été ajoutée à Jackson 2.5.0 pour indiquer à Jackson d'utiliser la convention de dénomination Java Bean - voir Jackson Issue 653 . Pour la compatibilité ascendante, la valeur par défaut de MapperFeature.USE_STD_BEAN_NAMING est false.
Cela a fonctionné pour moi. @JsonProperty Annotation sur les getters!
import com.fasterxml.jackson.annotation.JsonProperty;
public class PaytmRequestJson {
private String ORDERID;
private String MID;
private String CHECKSUMHASH;
@JsonProperty("ORDERID")
public String getORDERID() {
return ORDERID;
}
public void setORDERID(String ORDERID) {
this.ORDERID = ORDERID;
}
@JsonProperty("MID")
public String getMID() {
return MID;
}
public void setMID(String MID) {
this.MID = MID;
}
@JsonProperty("CHECKSUMHASH")
public String getCHECKSUMHASH() {
return CHECKSUMHASH;
}
public void setCHECKSUMHASH(String CHECKSUMHASH) {
this.CHECKSUMHASH = CHECKSUMHASH;
}
}
@JsonProperty
, tel que suggéré par la réponse actuelle, présente l'inconvénient d'être obligé de le répéter pour chaque propriété et qu'il est invasif (vous devez modifier la classe en cours de mappage).
Une approche plus générale consiste à fournir une stratégie personnalisée de nommage Propriété:
Java :
public class CobraCaseX extends PropertyNamingStrategy.PropertyNamingStrategyBase {
private static final Pattern REGEX = Pattern.compile("[A-Z]");
@Override
public String translate(String input) {
if (input == null)
return input; // garbage in, garbage out
if (!input.isEmpty() && Character.isUpperCase(input.charAt(0)))
input = input.substring(0, 1).toLowerCase() + input.substring(1);
return REGEX.matcher(input).replaceAll("_$0").toLowerCase();
}
}
Kotlin:
class CustomSnakeCase : PropertyNamingStrategy.PropertyNamingStrategyBase() {
private companion object {
val REGEX = Regex("[A-Z]")
}
override fun translate(input: String?) =
input?.decapitalize()?.replace(REGEX, "_$0")?.toLowerCase()
}
Utilisation:
new ObjectMapper()
.setPropertyNamingStrategy(new CustomSnakeCase())
.enable(MapperFeature.USE_STD_BEAN_NAMING)
Remarque: L’implémentation que j’ai donnée ci-dessus suppose que l’entrée est camelCase
(aucun début majuscule). USE_STD_BEAN_NAMING
est nécessaire pour gérer les préfixes à un caractère tels que aField
de manière cohérente.
L'implémentation fournit le mappage suivant, vous pouvez l'ajuster en fonction de vos besoins:
camelCase snake_case
----------------------------
simple simple
a a
sepaRated sepa_rated
iOException i_o_exception
xOffset x_offset
theWWW the_w_w_w
sepaRated32 sepa_rated32
sepa32Rated sepa32_rated