Почему Джексон 2 не признает первую заглавную букву, если ведущее слово верблюда - это всего лишь одна буква?
Я использую Spring 4 MVC с Jackson 2 для моей службы. Для одной из операций у меня есть объект запроса, у которого есть атрибут, где ведущее слово верблюда это только одна буква в длину:
private String aLogId;
Этот класс имеет соответственно названные геттеры и сеттеры:
public String getALogId() { return aLogId; }
public void setALogId(String aLogId) { this.aLogId = aLogId; }
Однако, когда я пытаюсь отправить запрос этой службе с использованием соответствующего свойства JSON:
{"aLogId":"This is a log id"}
Я получаю ответ 500 из структуры Spring, говорящий, что поле не распознано, и мой класс контроллера никогда не вызывается:
Не удалось прочитать JSON: нераспознанное поле "aLogId" (класс
Однако, когда я меняю "L" на нижний регистр, запрос десериализуется, как ожидалось, и мой класс контроллера попадает:
{"aLogId":"This is a log id"}
Почему Джексон ожидает, что "L" будет нижним регистром, когда он, очевидно, является вторым словом в условном случае для верблюда для атрибута и предназначен для того, чтобы быть в верхнем регистре? Это потому, что первое слово длится только одна буква?
В объекте запроса есть другие атрибуты, где первое слово содержит более одной буквы, а те, которые приписываются, не сталкиваются с этой же проблемой с несоответствием в случае.
Ответы
Ответ 1
Проблема, которую вы видите, связана с тем, что Джексон использует соглашения об именах Java Bean, чтобы выяснить свойства Json в классе Java.
Вот ссылка на конкретную проблему, которую вы видите. Рекомендация не состоит в том, чтобы использовать ни одну из первых двух букв в вашем поле. Если вы используете IDE, например IntelliJ или eclipse, и позволяете IDE генерировать для вас сеттеры, вы заметите, что происходит то же "поведение", что в итоге приведет к следующим методам:
public void setaLogId(String aLogId) {
this.aLogId = aLogId;
}
public String getaLogId() {
return aLogId;
}
Следовательно, когда вы изменили букву "L" на нижний регистр, Джексон смог определить поле, которое вы хотите отобразить.
Сказав вышесказанное, у вас все еще есть возможность использовать имя поля "aLogId" и заставить Джексона работать, все, что вам нужно сделать, это использовать аннотацию @JsonProperty
с aLogId
в ней.
@JsonProperty("aLogId")
private String aLogId;
Следующий тестовый код показывает, как это будет работать:
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();
}
}
}
Результат теста:
Serialization test: {"aLogId":"anId"}
Deserialization test: anotherId
Ответ 2
Я понимаю, что Джексон по умолчанию использует свое собственное соглашение об именах, которое очень близко, но не совсем то же самое, к соглашению об именах Java Bean. Параметр MapperFeature, MapperFeature.USE_STD_BEAN_NAMING, был добавлен в Jackson 2.5.0, чтобы сообщить Jackson использовать соглашение об именах Java Bean - см. Jackson Issue 653. Для обратной совместимости значение по умолчанию для MapperFeature.USE_STD_BEAN_NAMING является ложным.
Ответ 3
@JsonProperty
, как предлагается в текущем ответе, имеет недостаток, заключающийся в том, что вам нужно повторять его для каждого отдельного свойства и делать его инвазивным (вам нужно изменить отображаемый класс).
Более общий подход заключается в предоставлении собственной стратегии именования свойств:
Java:
public class CustomSnakeCase 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();
}
}
Котлин:
class CustomSnakeCase : PropertyNamingStrategy.PropertyNamingStrategyBase() {
private companion object {
val REGEX = Regex("[A-Z]")
}
override fun translate(input: String?) =
input?.decapitalize()?.replace(REGEX, "_$0")?.toLowerCase()
}
Использование:
new ObjectMapper()
.setPropertyNamingStrategy(new CustomSnakeCase())
.enable(MapperFeature.USE_STD_BEAN_NAMING)
Примечание:
Реализация, которую я предоставляю выше, предполагает, что вводом является camelCase
(без начала в верхнем регистре). USE_STD_BEAN_NAMING
необходим для обработки односимвольных префиксов, таких как aField
.
Реализация обеспечивает следующее отображение, вы можете настроить его в соответствии со своими потребностями:
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
Ответ 4
Это сработало для меня; @JsonProperty аннотации на геттеры!
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;
}
}