I am trying to create a standard way to serialize and deserialize for Enum on Jackson.
My serialize is easy:
public class EnumSerializer extends JsonSerializer<Enum<?>> {
#Override
public void serialize(Enum<?> data, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (data == null) {
gen.writeString("");
} else {
gen.writeString(data.name());
}
}
}
Now I am working on Deserializer:
public class EnumDeserializer extends JsonDeserializer<Enum<?>> {
#Override
public Enum<?> deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext)
throws IOException, JsonProcessingException {
String dataStr = jsonparser.getText();
if (dataStr == null || dataStr.isEmpty()) {
return null;
} else {
Class<Enum<?>> enumClass = null; // How can I get enumClass?
for(Enum<?> one: enumClass.getEnumConstants()){
if(one.name().equals(dataStr)){
return one;
}
}
return null;
}
}
}
But you can see I have trouble to get enumClass.
Could you please help me?
Thanks!
If you really want to create the custom EnumDeserializer you can see the implementation of Jackson:
com.fasterxml.jackson.databind.deser.std.EnumDeserializer
But as I can see you try to implement the standard behavior of Jackson.
Related
I am not able to get JsonDeserializer to process empty values. I have a DAO and a custom deserialiser like this.
Class Example {
#JsonDeserialize(using = ResetableValueDeserializer.class)
Resetable<String> property;
}
class ResetableValueDeserializer extends StdDeserializer<ResetableValue<String>> {
ResetableValueDeserializer() {
super(ResetableValue.class);
}
protected ResetableValueDeserializer(Class<?> vc) {
super(vc);
}
#Override
public ResetableValue<String> deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JacksonException {
JsonNode node = jp.readValueAsTree();
if (node.asText().isEmpty()) {
return null;
}
return new ResetableValue<>(node.asText());
}
#Override
public ResetableValue<String> getNullValue(DeserializationContext ctxt) throws JsonMappingException {
return ResetableValue.asReset();
}
#Override
public ResetableValue<String> getAbsentValue(DeserializationContext ctxt) {
return ResetableValue.asNotProvided();
}
But neither the deserialise() nor getAbsentValue() is triggered when I have an empty input JSON like this {}. While the getNull() method is handling input JSON {property: null}
did anyone tried to find a good solution to automatically convert all empty strings to null object on deserialization using JsonB (Yasson)?
I encountered this problem on migration from Jackson to Jsonb where empty string value in request produces deserialization exception since it cannot be parsed to object.
HTTP request payload:
{
fieldNameUid: '', // Java property is UUID type
}
Jackson had the following configuration:
public void customize(ObjectMapper mapper) {
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
SimpleModule module = new SimpleModule();
module.addDeserializer(
String.class,
new StdDeserializer<>(String.class) {
#Override
public String deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
String result = StringDeserializer.instance.deserialize(parser, context);
if (result == null || result.isBlank()) {
return null;
}
return result;
}
});
module.addDeserializer(byte[].class,
new StdDeserializer<>(byte[].class) {
#Override
public byte[] deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
String result = StringDeserializer.instance.deserialize(parser, context);
if (result == null || result.isBlank()) {
return null;
}
return result.getBytes(StandardCharsets.UTF_8);
}
});
mapper.registerModule(module);
}
and current Jsonb config:
public class JsonbObjectMapper implements JsonbConfigCustomizer {
#Override
public void customize(JsonbConfig jsonbConfig) {
jsonbConfig
.withDeserializers(new StringDeserializer(), new ByteArrayDeserializer(), new EnumDeserializer())
.withSerializers(new EnumSerializer());
}
public static class StringDeserializer implements JsonbDeserializer<String> {
#Override
public String deserialize(javax.json.stream.JsonParser jsonParser, javax.json.bind.serializer.DeserializationContext deserializationContext, Type type) {
final String str = jsonParser.getString();
return str == null || str.isBlank() ? null : str;
}
}
public static class ByteArrayDeserializer implements JsonbDeserializer<byte[]> {
#Override
public byte[] deserialize(javax.json.stream.JsonParser jsonParser, javax.json.bind.serializer.DeserializationContext deserializationContext, Type type) {
final String str = jsonParser.getString();
return str == null || str.isBlank() ? null : str.getBytes(StandardCharsets.UTF_8);
}
}
public static class EnumDeserializer implements JsonbDeserializer<Enum> {
#Override
public Enum deserialize(javax.json.stream.JsonParser jsonParser, javax.json.bind.serializer.DeserializationContext deserializationContext, Type type) {
final String str = jsonParser.getString();
if (str == null || str.isBlank()) {
return null;
}
for (final Enum en : ((Class<Enum>) type).getEnumConstants()) {
if (en.toString().equals(str)) {
return en;
}
}
return null;
}
}
public static class EnumSerializer implements JsonbSerializer<Enum> {
#Override
public void serialize(Enum anEnum, JsonGenerator jsonGenerator, SerializationContext serializationContext) {
jsonGenerator.write(anEnum == null ? null : anEnum.toString());
}
}
}
Since there is no alternative property for JsonB, I ended up writing custom deserialiser for UUID type.
I wanted to test my serializer which parses my java object to a json object. This is my Serializer class:
public class CountryCodeSerializer extends JsonSerializer<CountryCode> {
#Override
public void serialize(CountryCode value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (value == null) {
generator.writeString("{}");
} else {
generator.writeString(value.toString());
}
}
}
My test looks like this:
#Before
public void setUp() throws Exception {
stringJson = new StringWriter();
generator = new JsonFactory().createGenerator(stringJson);
provider = new ObjectMapper().getSerializerProvider();
countryCode = CountryCode.parse("us");
}
#Test
public void parsingNullReturnsNull() throws Exception {
assertThat(countryCodeSerializer.serialize(countryCode, generator, provider)).isEqualTo("{'countrycode':'us'}); //this doesn't work, since serialize() is void
//countryCodeSerializer.serialize(countryCode, generator, provider); //this throws an java.lang.NullPointerException
}
So how can I test my serializer? I tried other answers to similar questions, but nothing worked for me.
I use the serializer like this in my other clases:
#JsonSerialize(using = CountryCodeSerializer.class)
private CountryCode countryCode;
Ok thank you for your answers. I got it now this way and it works fine:
I changed my serializer a little bit:
public class CountryCodeSerializer extends JsonSerializer<CountryCode> {
#Override
public void serialize(CountryCode value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (null == value) {
throw new IllegalArgumentException("CountryCode is null");
} else {
generator.writeString(value.toString());
}
}
}
And here are my two tests:
public class CountryCodeSerializerTest {
private CountryCodeSerializer countryCodeSerializer;
private JsonGenerator jsonGenerator;
#Before
public void setUp() throws Exception {
countryCodeSerializer = new CountryCodeSerializer();
jsonGenerator = mock(JsonGenerator.class);
}
#Test
public void testNullCountryCodeThrowsIllegalArgumentException() throws Exception {
try {
countryCodeSerializer.serialize(null, jsonGenerator, null);
fail("An IllegalArgumentException should have been thrown.");
} catch (IllegalArgumentException e) {
//ok
}
}
#Test
public void testCountryCodeConvertedToJsonString() throws Exception {
countryCodeSerializer.serialize(CountryCode.parse("us"), jsonGenerator, null);
verify(jsonGenerator).writeString("us");
}
}
Something like this:
#Mock
private JsonGenerator generator;
#Test
public void testInstanceWithValue() {
//SETUP
String expectedValue = "test value";
CountryCode value = mock(CountryCode.class);
when(value.toString()).thenReturn(expectedValue);
// CALL
CountryCodeSerializer instance = new CountryCodeSerializer(value, generator, null);
// VERIFY
verify(generator).writeString(expectedValue);
}
#Test
public void testInstanceWithNull() {
//SETUP
CountryCode value = null;
// CALL
CountryCodeSerializer instance = new CountryCodeSerializer(value, generator, null);
// VERIFY
verify(generator).writeString("{}");
}
This can be achieved by creating a custom JsonGenerator that stores what is written to it.
class TestJsonGenerator extends JsonGenerator {
private StringBuilder stringBuilder = new StringBuilder();
...
#Override
public void writeString(String text) {
stringBuilder.append(text);
}
public String getText() {
return stringBuilder.toString();
}
}
Then you verify the generated content, without needing to check all the calls to writeString that were made:
TestJsonGenerator testGenerator = new TestJsonGenerator();
serializer.serialize(countryCode, testGenerator, provider);
assertThat(testGenerator.getText()).isEqualsTo("{ \"foo\": \"bar\" }");
I want to create a custom serializer which does a tiny bit of work and then leaves the rest for default serialization.
For example:
#JsonSerialize(using = MyClassSerializer.class)
public class MyClass {
...
}
public class MyClassSerializer extends JsonSerializer<MyClass> {
#Override
public void serialize(MyClass myClass, JsonGenerator generator,
SerializerProvider provider)
throws JsonGenerationException, IOException {
if (myClass.getSomeProperty() == someCalculationResult) {
provider.setAttribute("special", true);
}
generator.writeObject(myClass);
}
}
With the idea of creating other custom serializers for aggregated objects which behave differently based on the 'special' attribute value. However, the above code does not work, as it unsurprisingly goes into an infinite recursion.
Is there a way to tell jackson to use default serialization once I have set the attribute? I don't really want enumerate all the properties like many custom serializers as the class is fairly complex and I don't want to have to do dual maintenance with the serializer every time I change the class.
A BeanSerializerModifier will provide you access to the default serialization.
Inject a default serializer into the custom serializer
public class MyClassSerializer extends JsonSerializer<MyClass> {
private final JsonSerializer<Object> defaultSerializer;
public MyClassSerializer(JsonSerializer<Object> defaultSerializer) {
this.defaultSerializer = checkNotNull(defaultSerializer);
}
#Override
public void serialize(MyClass myclass, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (myclass.getSomeProperty() == true) {
provider.setAttribute("special", true);
}
defaultSerializer.serialize(myclass, gen, provider);
}
}
Create a BeanSerializerModifier for MyClass
public class MyClassSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass() == MySpecificClass.class) {
return new MyClassSerializer((JsonSerializer<Object>) serializer);
}
return serializer;
}
}
Register the serializer modifier
ObjectMapper om = new ObjectMapper()
.registerModule(new SimpleModule()
.setSerializerModifier(new MyClassSerializerModifier()));
#JsonSerialize(using = MyClassSerializer.class)
public class MyClass {
...
}
public class MyClassSerializer extends JsonSerializer<MyClass> {
#Override
public void serialize(MyClass myClass, JsonGenerator generator,
SerializerProvider provider)
throws JsonGenerationException, IOException {
if (myClass.getSomeProperty() == someCalculationResult) {
provider.setAttribute("special", true);
} else {
provider.defaultSerializeValue(myClass, generator);
}
}
}
if you are just writing an object as normal use the above
You can use #JsonGetter instead of using a custom serializer if that's the only change you want to make.
public class MyClass{
#JsonGetter("special")
protected boolean getSpecialForJackson() {
return myClass.getSomeProperty() == someCalculationResult;
}
}
To add to the chosen answer, the serializer implementation may also have to implement ContextualSerializer and ResolvableSerializer interfaces. Please take a look at a related issue here
https://github.com/FasterXML/jackson-dataformat-xml/issues/259
public class MyClassSerializer extends JsonSerializer<MyClass>
implements ContextualSerializer, ResolvableSerializer {
private final JsonSerializer<Object> defaultSerializer;
public MyClassSerializer(JsonSerializer<Object> defaultSerializer) {
this.defaultSerializer = checkNotNull(defaultSerializer);
}
#Override
public void serialize(MyClass myclass, JsonGenerator gen, SerializerProvider provider)
throws IOException {
if (myclass.getSomeProperty() == true) {
provider.setAttribute("special", true);
}
defaultSerializer.serialize(myclass, gen, provider);
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
if (defaultSerializer instanceof ContextualSerializer) {
JsonSerializer<?> contextual = ((ContextualSerializer)defaultSerializer).createContextual(prov, property);
return new MyClassSerializer((JsonSerializer<Object>)contextual);
}
return new MyClassSerializer(defaultSerializer);
}
#Override
public void resolve(SerializerProvider provider) throws JsonMappingException {
if (defaultSerializer instanceof ResolvableSerializer) {
((ResolvableSerializer)defaultSerializer).resolve(provider);
}
}
}
I am going to deserialize Json null value to Java Object empty string
I am able to make my custom deserializer, but when the Json value is null, it did not go into the deserializer.
How should I deserialize it?
Thanks in advance!
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext) throws IOException,
JsonProcessingException {
String str = jsonparser.getText();
try {
return (str == null) ? "" : str;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public CustomObjectMapper() {
SimpleModule _module = new SimpleModule("Module", new Version(1, 9, 10, "FINAL"));
_module.addDeserializer(String.class, new CustomStringDeserializer());
}
Thanks #nutlike
I do this by
#Override
public String getNullValue() {
return "";
}
Maybe it would be sufficient to overwrite the method getNullValue()?
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonparser,
DeserializationContext deserializationcontext) throws IOException,
JsonProcessingException {
return jsonparser.getText();
}
#Override
public String getNullValue() {
return "";
}
}