Как я могу использовать пользовательский сериализатор с Джексоном?
у меня есть два класса Java, которые я хочу сериализовать в JSON с помощью Jackson:
public class User {
public final int id;
public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
public class Item {
public final int id;
public final String itemNr;
public final User createdBy;
public Item(int id, String itemNr, User createdBy) {
this.id = id;
this.itemNr = itemNr;
this.createdBy = createdBy;
}
}
Я хочу сериализовать элемент в этот JSON:
{"id":7, "itemNr":"TEST", "createdBy":3}
С сериализованным пользователем, чтобы включить только id
. Я также смогу серилизовать все пользовательские объекты в JSON, например:
{"id":3, "name": "Jonas", "email": "jonas@example.com"}
так что я думаю, что мне нужно написать пользовательский сериализатор для Item
и попытался с помощью этого:
public class ItemSerializer extends JsonSerializer<Item> {
@Override
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeNumberField("itemNr", value.itemNr);
jgen.writeNumberField("createdBy", value.user.id);
jgen.writeEndObject();
}
}
Я сериализовать в JSON с помощью этого кода Джексон Практическое руководство: пользовательские сериализаторы:
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule",
new Version(1,0,0,null));
simpleModule.addSerializer(new ItemSerializer());
mapper.registerModule(simpleModule);
StringWriter writer = new StringWriter();
try {
mapper.writeValue(writer, myItem);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
но я получаю эту ошибку:
Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() (use alternative registration method?)
at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62)
at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54)
at com.example.JsonTest.main(JsonTest.java:54)
как я могу использовать пользовательский сериализатор с Джексоном?
вот как я бы сделал это с Gson:
public class UserAdapter implements JsonSerializer<User> {
@Override
public JsonElement serialize(User src, java.lang.reflect.Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(src.id);
}
}
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(User.class, new UserAdapter());
Gson gson = builder.create();
String json = gson.toJson(myItem);
System.out.println("JSON: "+json);
но мне нужно сделать это с Джексоном сейчас, так как Gson не поддерживает интерфейсы.
10 ответов:
как уже упоминалось, @JsonValue-это хороший способ. Но если вы не возражаете против пользовательского сериализатора, нет необходимости писать его для элемента, а скорее для пользователя - если это так, это было бы так же просто, как:
public void serialize(Item value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeNumber(id); }
еще одна возможность заключается в реализации
JsonSerializable
в этом случае регистрация не требуется.что касается ошибки; это странно - вы, вероятно, хотите перейти на более позднюю версию. Но это также безопаснее продлить
org.codehaus.jackson.map.ser.SerializerBase
поскольку он будет иметь стандартные реализации несущественных методы (т. е. все, кроме фактического вызова сериализации).
можно поставить
@JsonSerialize(using = CustomDateSerializer.class)
над любым полем даты объекта для сериализации.public class CustomDateSerializer extends SerializerBase<Date> { public CustomDateSerializer() { super(Date.class, true); } @Override public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)"); String format = formatter.format(value); jgen.writeString(format); } }
Я тоже пытался это сделать, и в примере кода на веб-странице Jackson есть ошибка, которая не включает тип (.class) в вызове метода addSerializer, который должен выглядеть следующим образом:
simpleModule.addSerializer(Item.class, new ItemSerializer());
другими словами, это строки, которые создают экземпляр simpleModule и добавляют сериализатор (с предыдущей неправильной строкой, закомментированной):
ObjectMapper mapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule("SimpleModule", new Version(1,0,0,null)); // simpleModule.addSerializer(new ItemSerializer()); simpleModule.addSerializer(Item.class, new ItemSerializer()); mapper.registerModule(simpleModule);
FYI: вот ссылка на правильный пример кода: http://wiki.fasterxml.com/JacksonFeatureModules
надеюсь, что это помогает!
Используйте @JsonValue:
public class User { int id; String name; @JsonValue public int getId() { return id; } }
@JsonValue работает только с методами, поэтому вы должны добавить метод getId. Вы должны быть в состоянии пропустить свой пользовательский сериализатор.
это модели поведения, которые я заметил, пытаясь понять сериализацию Джексона.
1) Предположим, что есть класс объекта и ученик класса. Я сделал все публичным и окончательным для удобства.
public class Classroom { public final double double1 = 1234.5678; public final Double Double1 = 91011.1213; public final Student student1 = new Student(); } public class Student { public final double double2 = 1920.2122; public final Double Double2 = 2324.2526; }
2) Предположим, что это сериализаторы, которые мы используем для сериализации объектов в JSON. WriteObjectField использует собственный сериализатор объекта, если он зарегистрирован в Object mapper; если нет, то он сериализует его как POJO. Этот writeNumberField исключительно принимает только примитивы в качестве аргументов.
public class ClassroomSerializer extends StdSerializer<Classroom> { public ClassroomSerializer(Class<Classroom> t) { super(t); } @Override public void serialize(Classroom value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { jgen.writeStartObject(); jgen.writeObjectField("double1-Object", value.double1); jgen.writeNumberField("double1-Number", value.double1); jgen.writeObjectField("Double1-Object", value.Double1); jgen.writeNumberField("Double1-Number", value.Double1); jgen.writeObjectField("student1", value.student1); jgen.writeEndObject(); } } public class StudentSerializer extends StdSerializer<Student> { public StudentSerializer(Class<Student> t) { super(t); } @Override public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { jgen.writeStartObject(); jgen.writeObjectField("double2-Object", value.double2); jgen.writeNumberField("double2-Number", value.double2); jgen.writeObjectField("Double2-Object", value.Double2); jgen.writeNumberField("Double2-Number", value.Double2); jgen.writeEndObject(); } }
3) зарегистрировать только DoubleSerializer с десятичной выходной паттерн
###,##0.000
, в SimpleModule и вывод:{ "double1" : 1234.5678, "Double1" : { "value" : "91,011.121" }, "student1" : { "double2" : 1920.2122, "Double2" : { "value" : "2,324.253" } } }
вы можете видеть, что сериализация POJO различается между double и Double, используя DoubleSerialzer для двойников и используя обычный строковый формат для двойников.
4) регистр DoubleSerializer и ClassroomSerializer, без Студентсериализатор. Мы ожидаем, что результат таков, что если мы пишем double как объект, он ведет себя как Double, и если мы пишем Double как число, он ведет себя как double. Переменная экземпляра Student должна быть записана как POJO и следовать приведенному выше шаблону, поскольку она не регистрируется.
{ "double1-Object" : { "value" : "1,234.568" }, "double1-Number" : 1234.5678, "Double1-Object" : { "value" : "91,011.121" }, "Double1-Number" : 91011.1213, "student1" : { "double2" : 1920.2122, "Double2" : { "value" : "2,324.253" } } }
5) зарегистрировать все сериализаторы. На выходе получается:
{ "double1-Object" : { "value" : "1,234.568" }, "double1-Number" : 1234.5678, "Double1-Object" : { "value" : "91,011.121" }, "Double1-Number" : 91011.1213, "student1" : { "double2-Object" : { "value" : "1,920.212" }, "double2-Number" : 1920.2122, "Double2-Object" : { "value" : "2,324.253" }, "Double2-Number" : 2324.2526 } }
именно так, как ожидалось.
еще одно важное замечание: если у вас есть несколько сериализаторов для тот же класс, зарегистрированный в том же модуле, затем модуль выберет сериализатор для этого класса, который был добавлен в список последним. Это не должно использоваться - это сбивает с толку, и я не уверен, насколько это последовательно
мораль: если вы хотите настроить сериализацию примитивов в объекте, вы должны написать свой собственный сериализатор для объекта. Вы не можете полагаться на сериализацию POJO Jackson.
взгляды Джексона JSON может быть более простой способ достижения ваших требований, особенно если у вас есть некоторая гибкость в формате JSON.
если
{"id":7, "itemNr":"TEST", "createdBy":{id:3}}
является приемлемым представлением, то это будет очень легко достичь с очень небольшим количеством кода.вы просто аннотируете поле имени пользователя как часть представления и указываете другое представление в своем запросе сериализации (не аннотированные поля будут включены по умолчанию)
например: Определите представления:
public class Views { public static class BasicView{} public static class CompleteUserView{} }
комментировать от пользователя:
public class User { public final int id; @JsonView(Views.CompleteUserView.class) public final String name; public User(int id, String name) { this.id = id; this.name = name; } }
и сериализовать запрос представления, которое не содержит поля, которое вы хотите скрыть (не аннотированные поля сериализуются по умолчанию):
objectMapper.getSerializationConfig().withView(Views.BasicView.class);
в моем случае (Spring 3.2.4 и Jackson 2.3.1), конфигурация XML для пользовательского сериализатора:
<mvc:annotation-driven> <mvc:message-converters register-defaults="false"> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="serializers"> <array> <bean class="com.example.business.serializer.json.CustomObjectSerializer"/> </array> </property> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
был необъяснимым образом перезаписан обратно по умолчанию чем-то.
это сработало для меня:
CustomObject.java
@JsonSerialize(using = CustomObjectSerializer.class) public class CustomObject { private Long value; public Long getValue() { return value; } public void setValue(Long value) { this.value = value; } }
CustomObjectSerializer.java
public class CustomObjectSerializer extends JsonSerializer<CustomObject> { @Override public void serialize(CustomObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException,JsonProcessingException { jgen.writeStartObject(); jgen.writeNumberField("y", value.getValue()); jgen.writeEndObject(); } @Override public Class<CustomObject> handledType() { return CustomObject.class; } }
нет конфигурации XML (
<mvc:message-converters>(...)</mvc:message-converters>
) необходимо в моем решении.
Я написал пример для пользовательского
Timestamp.class
сериализация / десериализация,но вы можете использовать его для чего угодно.при создании объекта mapper сделать что-то вроде этого:
public class JsonUtils { public static ObjectMapper objectMapper = null; static { objectMapper = new ObjectMapper(); SimpleModule s = new SimpleModule(); s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler()); s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler()); objectMapper.registerModule(s); }; }
например
java ee
вы можете инициализировать его с помощью этого:import java.time.LocalDateTime; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; @Provider public class JacksonConfig implements ContextResolver<ObjectMapper> { private final ObjectMapper objectMapper; public JacksonConfig() { objectMapper = new ObjectMapper(); SimpleModule s = new SimpleModule(); s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler()); s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler()); objectMapper.registerModule(s); }; @Override public ObjectMapper getContext(Class<?> type) { return objectMapper; } }
где сериализатор должен быть что-то вроде этого:
import java.io.IOException; import java.sql.Timestamp; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class TimestampSerializerTypeHandler extends JsonSerializer<Timestamp> { @Override public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { String stringValue = value.toString(); if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) { jgen.writeString(stringValue); } else { jgen.writeNull(); } } @Override public Class<Timestamp> handledType() { return Timestamp.class; } }
и десериализатор что-то вроде этого:
import java.io.IOException; import java.sql.Timestamp; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.SerializerProvider; public class TimestampDeserializerTypeHandler extends JsonDeserializer<Timestamp> { @Override public Timestamp deserialize(JsonParser jp, DeserializationContext ds) throws IOException, JsonProcessingException { SqlTimestampConverter s = new SqlTimestampConverter(); String value = jp.getValueAsString(); if(value != null && !value.isEmpty() && !value.equals("null")) return (Timestamp) s.convert(Timestamp.class, value); return null; } @Override public Class<Timestamp> handledType() { return Timestamp.class; } }