Различные имена свойства JSON при сериализации и десериализации
Возможно ли: иметь одно поле в классе, но разные имена для него во время сериализации / десериализации в библиотеке Джексона?
Например, у меня есть класс "Coordiantes".
class Coordinates{
int red;
}
Для десериализации из JSON нужно иметь такой формат:
{
"red":12
}
Но когда я сериализую объект, результат должен быть таким:
{
"r":12
}
Я пытался реализовать это, применяя @JsonProperty
аннотация как на геттере, так и на сеттере (с разными значениями):
class Coordiantes{
int red;
@JsonProperty("r")
public byte getRed() {
return red;
}
@JsonProperty("red")
public void setRed(byte red) {
this.red = red;
}
}
но я получил исключение:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: нераспознанное поле "красный"
14 ответов
Только что проверил и это работает:
public class Coordinates {
byte red;
@JsonProperty("r")
public byte getR() {
return red;
}
@JsonProperty("red")
public void setRed(byte red) {
this.red = red;
}
}
Идея заключается в том, что имена методов должны быть разными, поэтому Джексон анализирует его как разные поля, а не как одно поле.
Вот тестовый код:
Coordinates c = new Coordinates();
c.setRed((byte) 5);
ObjectMapper mapper = new ObjectMapper();
System.out.println("Serialization: " + mapper.writeValueAsString(c));
Coordinates r = mapper.readValue("{\"red\":25}",Coordinates.class);
System.out.println("Deserialization: " + r.getR());
Результат:
Serialization: {"r":5}
Deserialization: 25
Вы можете использовать @jsonAlias, который был представлен в Джексоне 2.9.0
Пример:
public class Info {
@JsonAlias({ "r", "red" })
public String r;
}
Вы можете использовать комбинацию @JsonSetter и @JsonGetter для управления десериализацией и сериализацией вашего свойства соответственно.
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonGetter;
class Coordinates {
private int red;
//# Used during serialization
@JsonGetter("r")
public int getRed() {
return red;
}
//# Used during deserialization
@JsonSetter("red")
public void setRed(int red) {
this.red = red;
}
}
Я бы связал две разные пары геттеров / сеттеров с одной переменной:
class Coordinates{
int red;
@JsonProperty("red")
public byte getRed() {
return red;
}
public void setRed(byte red) {
this.red = red;
}
@JsonProperty("r")
public byte getR() {
return red;
}
public void setR(byte red) {
this.red = red;
}
}
Вы можете использовать этот вариант:
import lombok.Getter;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
//...
@JsonProperty(value = "rr") // for deserialization
@Getter(onMethod_ = {@JsonGetter(value = "r")}) // for serialization
private String rrrr;
с геттером Ломбока
Возможно иметь нормальную пару геттер / сеттер. Вам просто нужно указать режим доступа в @JsonProperty
Вот тестовый модуль для этого:
public class JsonPropertyTest {
private static class TestJackson {
private String color;
@JsonProperty(value = "device_color", access = JsonProperty.Access.READ_ONLY)
public String getColor() {
return color;
};
@JsonProperty(value = "color", access = JsonProperty.Access.WRITE_ONLY)
public void setColor(String color) {
this.color = color;
}
}
@Test
public void shouldParseWithAccessModeSpecified() throws Exception {
String colorJson = "{\"color\":\"red\"}";
ObjectMapper mapper = new ObjectMapper();
TestJackson colotObject = mapper.readValue(colorJson, TestJackson.class);
String ser = mapper.writeValueAsString(colotObject);
System.out.println("Serialized colotObject: " + ser);
}
}
Я получил вывод следующим образом:
Serialized colotObject: {"device_color":"red"}
Это было не то, что я ожидал как решение (хотя это законный вариант использования). Мое требование состояло в том, чтобы позволить существующему клиенту с ошибками (мобильное приложение, которое уже выпущено) использовать альтернативные имена.
Решение заключается в предоставлении отдельного метода установки, подобного этому:
@JsonSetter( "r" )
public void alternateSetRed( byte red ) {
this.red = red;
}
Аннотирование с @JsonAlias
который был представлен с Jackson 2.9+, не говоря уже о @JsonProperty
для десериализуемого элемента с более чем одним псевдонимом (разные имена для свойства json) работает нормально.
я использовал com.fasterxml.jackson.annotation.JsonAlias
для согласованности пакета с com.fasterxml.jackson.databind.ObjectMapper
для моего варианта использования.
Например:
@Data
@Builder
public class Chair {
@JsonAlias({"woodenChair", "steelChair"})
private String entityType;
}
@Test
public void test1() {
String str1 = "{\"woodenChair\":\"chair made of wood\"}";
System.out.println( mapper.readValue(str1, Chair.class));
String str2 = "{\"steelChair\":\"chair made of steel\"}";
System.out.println( mapper.readValue(str2, Chair.class));
}
просто отлично работает.
Я знаю, что это старый вопрос, но для меня он заработал, когда я понял, что он конфликтует с библиотекой Gson, поэтому, если вы используете Gson, используйте @SerializedName("name")
вместо того @JsonProperty("name")
надеюсь это поможет
В моем случае мне пришлось читать входные данные на бразильском португальском и генерировать выходные данные на английском.
Итак, обходной путь, который сработал для меня, использовал
@JsonAlias
вместо того
@JsonProperty
:
// pseudo-java
@Value
public class User {
String username;
public User(
@JsonAlias("nome_usuario") String username) {
// ...
}
}
Используйте для атрибута как JsonAlias, так и JsonProperty.
data class PayoutMethodCard(
@JsonProperty("payment_account_id")
@JsonAlias("payout_account_id")
val payoutAccountId: Long
)
В этом случаеpaymentAccountId
может сериализоваться из JSON либо с помощью, либо с помощьюpayout_account_id
, но при десериализации обратно в JSON будет использоваться JSONProperty, иpayment_account_id
будет использован.
Должно быть, они включили это как функцию, потому что теперь установка другого @JsonProperty
для метода получения и установки получаются в точности то, что вы ожидаете (другое имя свойства при сериализации и десериализации для одного и того же поля). Джексон версия 2.6.7
Вы можете написать сериализованный класс для этого:
символ общественного класса
{частный символ String;
private String name;
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Открытый класс SymbolJsonSerializer расширяет JsonSerializer {
@Override
public void serialize(Symbol symbol, JsonGenerator jgen, SerializerProvider serializers) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("symbol", symbol.getSymbol());
//Changed name to full_name as the field name of Json string
jgen.writeStringField("full_name", symbol.getName());
jgen.writeEndObject();
}
}
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Symbol.class, new SymbolJsonSerializer());
mapper.registerModule(module);
//only convert non-null field, option...
mapper.setSerializationInclusion(Include.NON_NULL);
String jsonString = mapper.writeValueAsString(symbolList);
Для парней из Котлина:
data class TestClassDTO(
@JsonProperty("user_name")
val username: String
)
Вы успешно справитесь{"user_name": "John"}
из полезной нагрузки POST в RestControllers
Но когда вам нужно сериализовать обратно с тем же именем @JsonProperty, вы можете использовать этот рефлекторный подход
fun Any.forceSerialize(separator: String, sorted: Boolean = false): String {
var fieldNameToAnnotatedNameMap = this.javaClass.declaredFields.map { it.name }.associateWith { fieldName ->
val jsonFieldName =
this::class.primaryConstructor?.parameters?.first { it.name == fieldName }?.annotations?.firstOrNull { it is JsonProperty }
val serializedName = if (jsonFieldName != null) (jsonFieldName as JsonProperty).value else fieldName
serializedName
}
if (sorted)
fieldNameToAnnotatedNameMap = fieldNameToAnnotatedNameMap.toList().sortedBy { (_, value) -> value}.toMap()
return fieldNameToAnnotatedNameMap.entries.joinToString(separator) { e ->
val field = this::class.memberProperties.first { it.name == e.key }
"${e.value}=${field.javaGetter?.invoke(this)}"
}
}