Как использовать пользовательский Serializer с Jackson?

У меня есть два classа 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} 

с User serialized, чтобы включать только id . Я также смогу выполнить сериализацию всех пользовательских объектов в JSON, например:

 {"id":3, "name": "Jonas", "email": "[email protected]"} 

Поэтому я предполагаю, что мне нужно написать собственный сериализатор для Item и попытался с этим:

 public class ItemSerializer extends JsonSerializer { @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 с этим кодом из Howson Jackson: пользовательские сериализаторы :

 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) 

Как я могу использовать пользовательский Serializer с Jackson?


Так я бы сделал это с Гсеном:

 public class UserAdapter implements JsonSerializer { @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); 

Но мне нужно сделать это с jacksonом сейчас, так как у Gson нет поддержки интерфейсов.

Как уже упоминалось, @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 { 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. Вы должны быть в состоянии пропустить свой собственный сериализатор в целом.

Это модели поведения, которые я заметил, пытаясь понять сериализацию jacksonа.

1) Предположим, что есть объект Classroom и class Student. Я сделал все публичным и окончательным для простоты.

 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 использует собственный сериализатор объекта, если он зарегистрирован с помощью mapper объекта; если нет, то он сериализует его как POJO. Тип writeNumberField принимает только примитивы как аргументы.

 public class ClassroomSerializer extends StdSerializer { public ClassroomSerializer(Class 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 { public StudentSerializer(Class 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 с шаблоном вывода DecimalFormat ###,##0.000 , в SimpleModule и вывод:

 { "double1" : 1234.5678, "Double1" : { "value" : "91,011.121" }, "student1" : { "double2" : 1920.2122, "Double2" : { "value" : "2,324.253" } } } 

Вы можете видеть, что сериализация POJO различает двойные и двойные значения, используя DoubleSerialzer for Doubles и используя обычный формат String для удвоений.

4) Зарегистрируйте DoubleSerializer и ClassroomSerializer без StudentSerializer. Мы ожидаем, что вывод таков, что если мы будем писать 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 } } 

точно так, как ожидалось.

Еще одно важное замечание. Если у вас есть несколько сериализаторов для одного classа, зарегистрированного в одном модуле, тогда модуль выберет сериализатор для этого classа, который недавно добавлен в список. Это не должно использоваться – это запутывает, и я не уверен, насколько это согласовано.

Мораль: если вы хотите настроить сериализацию примитивов в своем объекте, вы должны написать свой собственный сериализатор для объекта. Вы не можете рассчитывать на сериализацию POJO Jackson.

Jackson’s JSON Views может быть более простым способом достижения ваших требований, особенно если у вас есть определенная гибкость в вашем формате 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 для пользовательского сериализатора:

                

был в необъяснимом виде, перезаписанным чем-то по умолчанию.

Это сработало для меня:

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 { @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 handledType() { return CustomObject.class; } } 

В моем решении не требуется конфигурация XML ( (...) ).

Я написал пример для пользовательской сериализации / десериализации 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 { 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 { @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 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 { @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 handledType() { return Timestamp.class; } } 

Вы должны переопределить метод handledType, и все будет работать

 @Override public Class handledType() { return Item.class; } 

Если ваше единственное требование в вашем пользовательском сериализаторе – пропустить сериализацию поля имени User , отметьте его как переходный . jackson не будет сериализовать или десериализовать переходные поля.

[см. также: Почему Java имеет переходные поля? ]

  • ТипNameHandling предостережение в Newtonsoft Json
  • jackson не десериализует общий список, который он сериализовал
  • Уничтожить stream массива json по одному элементу за раз
  • Можно ли выполнять бинарную сериализацию .NET в случае, если у вас нет исходного кода classа?
  • Сериализация / десериализация пользовательской коллекции с дополнительными свойствами с помощью Json.Net
  • Сериализация с Qt
  • Как десериализовать объект JObject для .NET
  • jQuery serializeArray не включает кнопку отправки, которая была нажата
  • Как вы делаете глубокую копию объекта в .NET (например, C #)?
  • Использование readObject / writeObject в сериализации
  • Как сериализовать объект в CSV-файл?
  • Давайте будем гением компьютера.