I want to serialize few fields of my class in custom way using jackson. So i wrote a custom serializer for this.But my problem is i am not able to get the name of the field in custom serializer. My POJO class is
public static class Foo {
public String foo = "a";
#JsonSerialize(using = CustomSerializer.class)
public String bar = "b";
#JsonSerialize(using = CustomSerializer.class)
public String foobar = "c";
}
And my custom serializer class is
public class CustomSerializer extends JsonSerializer<String>
{
#Override
public void serialize(String t, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException
{
if(field.name.equals("a"))
//do this
else if(filed.name.equals("b"))
//do that
}
}
Here i want get the name of field which is being serialized.
How can i get the name of fields "a" and "b" in custom serializer ?
Thanks
I think, this is not possible now. But you can create two separate serializers for each property. I know, this a little workaround, but it should work.
Related
I'm writing a application using Spring boot and jackson for JSON parsing. I need to handle another service which produces JSON like this:
{
"task-id": 5081,
"task-created-on": {
"java.util.Date": 1631022026000
}
}
Notably, certain fields like the date field here are serialized into a map with a single key-value pair, where the key is a java classname and the value is the actual value of the field.
I've been going through the jackson documentation and haven't found anything about this format. Is there a way to configure jackson to produce and parse fields in this format?
At a minimum, I need to handle dates formatted this way. But I believe the service also uses this format for other objects, where the map key will be the name of some arbitrary java class and the value will be a map of its own. So I'd be interested in a solution that handles more than just dates if possible.
It can be easily done with custom serializer in Jackson by following steps.
First, create objects for serialization as follows:
class MyDateObject {
private Date date;
//general getter/setter
}
class Task {
#JsonProperty("task-id")
private int taskId;
#JsonProperty("task-created-on")
private MyDateObject taskCreatedOn;
//general getters/setters
}
Second, define your custom serializer: (Please note that I used myDateObject.getDate().getClass().getName() to get the class name of date field.)
class DateSerializer extends StdSerializer<MyDateObject> {
public DateSerializer() {
this(null);
}
protected DateSerializer(Class<MyDateObject> t) {
super(t);
}
#Override
public void serialize(MyDateObject myDateObject, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField(myDateObject.getDate().getClass().getName(), myDateObject.getDate().getTime());
jsonGenerator.writeEndObject();
}
}
Finally, register the serializer with ObjectMapper for the MyDateObject class and perform the serialization:
MyDateObject myDateObject = new MyDateObject();
myDateObject.setDate(new Date());
Task task = new Task();
task.setTaskId(5081);
task.setTaskCreatedOn(myDateObject);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(MyDateObject.class, new DateSerializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(simpleModule);
System.out.println(objectMapper.writeValueAsString(task));
The expected output is:
{"task-id":5081,"task-created-on":{"java.util.Date":1633402076254}}
Please refer to Jackson – Custom Serializer for more information.
It is possible to solve the issue with the use of a custom JsonSerializer and applying the JsonSerialize over the fields in the pojo you are interested like below :
public class Task {
#JsonProperty("task-id")
private int taskId;
#JsonProperty("task-created-on")
#JsonSerialize(using = ObjectSerializer.class)
Date taskCreatedOn;
}
The custom serializer will use the JsonGenerator.html#writeObjectField to serialize a generic object (Date or other java class) as propertyname : {"classname" : value} :
public class ObjectSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object t, JsonGenerator jg, SerializerProvider sp) throws IOException {
jg.writeStartObject();
jg.writeObjectField(t.getClass().getName(), t);
jg.writeEndObject();
}
}
#AllArgsConstructor
#Getter
public enum MemberType {
INTERN("name_intern", 1),
EMPLOYEE("name_employee", 10);
private String name;
private int workingMonth;
}
Here is my enum. I want to convert Enum class to JSON string with some constraint.
I want to MemberType has no dependency with Jackson
I want to convert MemberType.INTERN to {id:INTERN, name:"name_intern", workingMonth:10}.
I have lots of Enums want to convert like above. And Their number of property is different each other.
I want resolve this problem through just one global configuration.
I don't want to use explicit java reflection.
Is there a solution that meets the above constraints?
You can use #JsonFormat annotation like this:
#JsonFormat(shape=JsonFormat.Shape.OBJECT)
public enum MemberType { ... }
or you can use #JsonValue annotation like this:
public enum MemberType {
[...]
#JsonValue
public String getName() {
return name;
}
}
or maybe a CustomSerializer for Enum, you can find more details here.
If you implement JsonSerializer,you can custom serialization.
An example is shown below.
#JsonComponent
public final class MediaTypeJsonComponent {
public static class Serializer extends JsonSerializer<MemberType> {
#Override
public void serialize(MemberType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeStringField("id", value.name());
gen.writeNumberField("workingMonth", value.getWorkingMonth());
gen.writeStringField("name", value.getName());
gen.writeEndObject();
}
}
//
// If you need,write code.
//public static class Deserializer extends JsonDeserializer<Customer> {
//}
}
Another way is to implement JsonSerialize.
If you want more information, you should refer to:
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonComponent.html
https://www.baeldung.com/jackson-custom-serialization
How do I use a custom Serializer with Jackson?
https://www.baeldung.com/jackson-serialize-enums
Imagine the following scenario:
class <T> Foo<T> {
....
}
class Bar {
Foo<Something> foo;
}
I want to write a custom Jackson deserializer for Foo. In order to do that (for example, in order to deserialize Bar class that has Foo<Something> property), I need to know the concrete type of Foo<T>, used in Bar, at deserialization time (e.g. I need to know that T is Something in that particluar case).
How does one write such a deserializer? It should be possible to do it, since Jackson does it with typed collections and maps.
Clarifications:
It seems there are 2 parts to solution of the problem:
1) Obtain declared type of property foo inside Bar and use that to deserialize Foo<Somehting>
2) Find out at deserialization time that we are deserializing property foo inside class Bar in order to successfully complete step 1)
How does one complete 1 and 2 ?
You can implement a custom JsonDeserializer for your generic type which also implements ContextualDeserializer.
For example, suppose we have the following simple wrapper type that contains a generic value:
public static class Wrapper<T> {
public T value;
}
We now want to deserialize JSON that looks like this:
{
"name": "Alice",
"age": 37
}
into an instance of a class that looks like this:
public static class Person {
public Wrapper<String> name;
public Wrapper<Integer> age;
}
Implementing ContextualDeserializer allows us to create a specific deserializer for each field in the Person class, based on the generic type parameters of the field. This allows us to deserialize the name as a string, and the age as an integer.
The complete deserializer looks like this:
public static class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {
private JavaType valueType;
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
WrapperDeserializer deserializer = new WrapperDeserializer();
deserializer.valueType = valueType;
return deserializer;
}
#Override
public Wrapper<?> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
Wrapper<?> wrapper = new Wrapper<>();
wrapper.value = ctxt.readValue(parser, valueType);
return wrapper;
}
}
It is best to look at createContextual here first, as this will be called first by Jackson. We read the type of the field out of the BeanProperty (e.g. Wrapper<String>) and then extract the first generic type parameter (e.g. String). We then create a new deserializer and store the inner type as the valueType.
Once deserialize is called on this newly created deserializer, we can simply ask Jackson to deserialize the value as the inner type rather than as the whole wrapper type, and return a new Wrapper containing the deserialized value.
In order to register this custom deserializer, we then need to create a module that contains it, and register that module:
SimpleModule module = new SimpleModule()
.addDeserializer(Wrapper.class, new WrapperDeserializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
If we then try to deserialize the example JSON from above, we can see that it works as expected:
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person.name.value); // prints Alice
System.out.println(person.age.value); // prints 37
There are some more details about how contextual deserializers work in the Jackson documentation.
If the target itself is a generic type then property will be null, for that you'll need to get the valueTtype from the DeserializationContext:
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
if (property == null) { // context is generic
JMapToListParser parser = new JMapToListParser();
parser.valueType = ctxt.getContextualType().containedType(0);
return parser;
} else { // property is generic
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
JMapToListParser parser = new JMapToListParser();
parser.valueType = valueType;
return parser;
}
}
This is how you can access/resolve {targetClass} for a Custom Jackson Deserializer. Of course you need to implement ContextualDeserializer interface for this.
public class WPCustomEntityDeserializer extends JsonDeserializer<Object>
implements ContextualDeserializer {
private Class<?> targetClass;
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
//Your code here to customize deserialization
// You can access {target class} as targetClass (defined class field here)
//This should build some {deserializedClasObject}
return deserializedClasObject;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property){
//Find here the targetClass to be deserialized
String targetClassName=ctxt.getContextualType().toCanonical();
try {
targetClass = Class.forName(targetClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return this;
}
}
For my use case, none of the above solutions worked, so I had to write a custom module. You can find my implementation on GitHub.
I wanted to write a deserializer that automatically removes blank Strings from Lists.
There is a good known case when we unwrap nested object and write its fields into the main object, and I need to make an inverse task.
I have a POJO:
class A {
private String id = "id1";
#JsonWrap("properties")
private String property1 = "...";
#JsonWrap("properties")
private String property2 = "...";
// getters and setters
}
Default serializer will produce as expected
{
"id": "id1",
"property1": "...",
"property2": "..."
}
But, my JSON should match some specification, and to do that, I need to wrap property1 and property2 inside properties wrapper. So the result should looks like:
{
"id": "id1",
"properties":
{
"property1": "...",
"property2": "..."
}
}
I don't want to change the structure of the POJO so I see 3 possible ways:
Write custom serializer. But as it seems to me, to write such serializer will takes more efforts then serialize objects by hands.
Create proxy Java object which will reflect the right structure of JSON, and serialize such proxy.
Modify JSON after it have been generated. (I'm afraid it would be a great overhead for rereading and rewriting of JSON).
Does anybody make such Serializer or maybe know another options to generate JSON with the structure I need?
For custom serializer I want to reuse standard BeanSerializer, I dont want to write out all fields manually:
Hide annotated fields.
Write out bean, without annotated fields, but don't close object. (Don't call jgen.writeEndObject();)
Write out wrapped fields.
Close object.
To get that functionality without altering your model, take a look at writing a custom serializer to accomplish what Jackson can't figure out natively. We annotate the model class A with specific directions to use a defined serializer, and then use the JsonGenerator to specifically define the structure we are after.
#JsonSerialize(using = ASerializer.class)
class A {
private String field1;
private String innerField1;
private String innerField2;
// getters and setters
public static class ASerializer extends JsonSerializer<A> {
#Override
public void serialize(A value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("field1", value.getField1());
jgen.writeObjectFieldStart("wrapper");
jgen.writeStringField("innerField1", value.getInnerField1());
jgen.writeStringField("innerField2", value.getInnerField2());
jgen.writeEndObject();
jgen.writeEndObject();
}
}
}
I used a static inner class in this case, but feasibly you can place the Serializer wherever best fits your project structure based on visibility. For one-off special case serializers, this is what I tend to do.
It sounds like you need to create a Custom Serializer: http://wiki.fasterxml.com/JacksonHowToCustomSerializers
Of course, if you are creating Java objects from a similar JSON structure you'll likely need to create a Custom Deserializer as well.
Remember, you can always use reflection to create a 'generic' serializer if you find many of your objects share a similar structure.
You need change your model.
#JsonSerialize(using = ASerializer.class)
class A {
private String id;
private String property1;
private String property2;
// getters and setters
public static class ASerializer extends JsonSerializer<A> {
#Override
public void serialize(A value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("id", value.getId());
jgen.writeObjectFieldStart("properties");
jgen.writeStringField("property1", value.getProperty1());
jgen.writeStringField("property2", value.getProperty2());
jgen.writeEndObject();
jgen.writeEndObject();
}
}
}
Run in main:
A a = new A();
a.setId("id1");
a.setProperty1("...");
a.setProperty2("...");
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer();
String json = writer.writeValueAsString(a);
System.out.println(json);
output:
{"id":"id1","properties":{"property1":"...","property2":"..."}}
In order to impose certain checks on classes that Jackson might deserialize to, I'd like to be able to easily find any such classes. However, in standard usage, Jackson can deserialize into a completely normal looking class that has no Jackson annotations.
A colleague mentioned having previously seen some way to setup Jackson to only successfully deserialize classes that are explicitly annotated as being able to do so. This would present an easy solution, as I could then just find classes with such an annotation. However, looking through all the Jackson docs, I can't find this functionality. Does anyone know where it's to be found/is this deprecated?
Consider an option where you mark your "json" classes with a custom annotation, then set a special annotation introspector which would fail to serialize all the other classes from your application.
Note that you will need to be able differentiate between the standard classes such as primitives, string, collection, etc., which don't have the custom annotation, and the classes from your application which shall not be processed.
Here is an example:
package stackoverflow;
public class JacksonTracking {
#Retention(RetentionPolicy.RUNTIME)
public static #interface Json {
}
#Json
public static class A {
public final String field1;
public A(String field1) {
this.field1 = field1;
}
}
public static class B {
public final String field2;
public B(String field2) {
this.field2 = field2;
}
}
public static class UnsupportedSerializer extends JsonSerializer.None {
private final Class<?> type;
public UnsupportedSerializer(Class<?> type) {
this.type = type;
}
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
throw new UnsupportedOperationException("Unsupported type: " + type);
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public Object findSerializer(Annotated a) {
if (a instanceof AnnotatedClass
&& a.getRawType().getPackage().getName().startsWith("stackoverflow")
&& !a.hasAnnotation(Json.class)) {
return new UnsupportedSerializer(a.getRawType());
}
return super.findSerializer(a);
}
});
System.out.println(mapper.writeValueAsString(new A("value1")));
System.out.println(mapper.writeValueAsString(new B("value2")));
}
}
Output:
{"field1":"value1"}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Unsupported type: class stackoverflow.JacksonTracking$B
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:125)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:2866)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:2323)
at stackoverflow.JacksonTracking.main(JacksonTracking.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.UnsupportedOperationException: Unsupported type: class stackoverflow.JacksonTracking$B
at stackoverflow.JacksonTracking$UnsupportedSerializer.serialize(JacksonTracking.java:52)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:114)
... 8 more
On the ObjectMapper that you use for deserialization you can configure auto detection of fields, methods, and creators. If you want to enforce this across your entire application I would recommend extending ObjectMapper and using the custom implementation everywhere. Then any additional config you want to impose can live there as well. Something like this:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
}
}
You could of course just call setVisibility anywhere you declare an ObjectMapper instead:
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);