I use Jackson 2.2.3 to serialize POJOs to JSON. Then I had the problem, that I couldn't serialize recursive structures...I solved this problem by using #JsonIdentityInfo => works great.
But, I don't want this annotation on the top of my POJO.
So my question is: Is there any other possibility to set the default behavior of my ObjectMapper to use the feature for every POJO?
So I want to transform this annotation code
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
to something like
ObjectMapper om = new ObjectMapper();
om.setDefaultIdentityInfo(ObjectIdGenerators.IntSequenceGenerator.class, "#id");
Any ideas?
You can achieve that using the Jackson mix-in annotations or the Jackson annotation introspector.
Here is an example showing both methods:
public class JacksonJsonIdentityInfo {
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
static class Bean {
public final String field;
public Bean(final String field) {this.field = field;}
}
static class Bean2 {
public final String field2;
public Bean2(final String field2) {this.field2 = field2;}
}
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id2")
static interface Bean2MixIn {
}
static class Bean3 {
public final String field3;
public Bean3(final String field3) {this.field3 = field3;}
}
static class MyJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
#Override
public ObjectIdInfo findObjectIdInfo(final Annotated ann) {
if (ann.getRawType() == Bean3.class) {
return new ObjectIdInfo(
PropertyName.construct("#id3", null),
null,
ObjectIdGenerators.IntSequenceGenerator.class,
null);
}
return super.findObjectIdInfo(ann);
}
}
public static void main(String[] args) throws JsonProcessingException {
final Bean bean = new Bean("value");
final Bean2 bean2 = new Bean2("value2");
final Bean3 bean3 = new Bean3("value3");
final ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Bean2.class, Bean2MixIn.class);
mapper.setAnnotationIntrospector(new MyJacksonAnnotationIntrospector());
System.out.println(mapper.writeValueAsString(bean));
System.out.println(mapper.writeValueAsString(bean2));
System.out.println(mapper.writeValueAsString(bean3));
}
}
Output:
{"#id":1,"field":"value"}
{"#id2":1,"field2":"value2"}
{"#id3":1,"field3":"value3"}
After several months and a lot of research, I've implemented my own solution to keep my domain clear of jackson dependencies.
public class Parent {
private Child child;
public Child getChild(){return child;}
public void setChild(Child child){this.child=child;}
}
public class Child {
private Parent parent;
public Child getParent(){return parent;}
public void setParent(Parent parent){this.parent=parent;}
}
First, you have to declare each of your entities of the bidirectional relationship:
public interface BidirectionalDefinition {
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Parent.class)
public interface ParentDef{};
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Child.class)
public interface ChildDef{};
}
After that, the object mapper can be automatically configured:
ObjectMapper om = new ObjectMapper();
Class<?>[] definitions = BidirectionalDefinition.class.getDeclaredClasses();
for (Class<?> definition : definitions) {
om.addMixInAnnotations(definition.getAnnotation(JsonIdentityInfo.class).scope(), definition);
}
Related
I have a JSON string that I want to deserialize into a class. The JSON looks like so:
{ "data": { "name": "Box 1", "size": "10x20" } }
I can deserialize this into the following class:
#Builder
#Value
#JsonDeserialize(builder = Box1.Box1Builder.class)
public class Box1 {
#JsonProperty("data")
Box1Data data;
public static Box1 of(String json) throws IOException {
return new ObjectMapper().readValue(json, Box1.class);
}
#Builder
#Value
#JsonDeserialize(builder = Box1Data.Box1DataBuilder.class)
static class Box1Data {
#JsonProperty("name")
String name;
#JsonProperty("size")
String size;
}
}
The above class looks clumsy since it has a useless hierarchy of data. I can get rid of it like so:
#Builder
#Value
#JsonDeserialize(using = Box2Deserializer.class)
public class Box2 {
#JsonProperty("name")
String name;
#JsonProperty("size")
String size;
public static Box2 of(String json) throws IOException {
return new ObjectMapper().readValue(json, Box2.class);
}
static class Box2Deserializer extends JsonDeserializer<Box2> {
#Override
public Box2 deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
var node = jsonParser.getCodec().readTree(jsonParser);
var dataNode = node.get("data");
return Box2.builder()
.name(dataNode.get("name").toString())
.size(dataNode.get("size").toString())
.build();
}
}
}
But here, I hit a dead-end. I want the size field to be parsed into a Dimension instance. I can write a custom deserializer for size that parses a String and returns a proper Dimension, but I cannot use it via field annotations (#JsonDeserialize(using = SizeDeserializer.class) since the presence of JsonDeserialize class annotation forces it to be ignored in the case for Box1, and in the case for Box2, it's ignored cuz I'm building the box manually.
Is there an elegant solution to all this mess? What I want is to read the given JSON into a class like this:
#Builder
#Value
public class Box3 {
#JsonProperty("name")
String name;
#JsonProperty("size")
Dimension size;
public static Box3 of(String json) {
...
}
}
Thanks!
Asim
I will add to #Iprakashv solution, besides only the needs for the JsonRootName type annotation and mapper serialization / deserialization for root node wrapping, you only need a custom type converter from a raw type to your custom type:
#Builder
#Value
#JsonRootName("data")
public class Box {
#JsonProperty("name")
String name;
#JsonDeserialize(converter = StringToDimensionConverter.class)
#JsonProperty("size")
Dimension size;
public static Box of(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
return mapper.readValue(json, Box.class);
}
private static class StringToDimensionConverter extends StdConverter<String, Dimension> {
#Override
public DataWrapper.Box1Data.Dimension convert(String s) {
return new DataWrapper.Box1Data.Dimension(s);
}
}
}
You actually do not need a custom deserializer and the #JsonDeserialize annotation. The ObjectMapper provides a configuration to enable wrapping/unwrapping a root value which can be provided using the #JsonRootName annotation over the Wrapper object class.
#Builder
#Value
#JsonRootName("data")
public class Box {
#JsonProperty("name")
String name;
#JsonProperty("size")
String size;
public static Box of(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
return mapper.readValue(json, Box.class);
}
}
PS: Totally missed the Dimension part in the question, for that, you can use a custom deserializer as mentioned in other answer.
I need to create a new annotation which is used to ignore a field in the output JSON file when the environment variable var == false. I tried to use JsonAnnotationIntrospector, but could not get the expected output.
public class Vehicle {
String vehicle_name;
String vehicle_model;
//getters and setters
#MyAnnotation
public String getVehicle_model() {
return vehicle_model;
}
}
Here I need to remove vehicle_model attribute when the environmental variable var == false.
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
#JsonIgnore
public #interface MyAnnotation {
}
This is the declaration of my custom Annotation.
Can Someone tell me how should I write the Introspector part to get the functionality I need?
Thanks in advance.
Edit: My attempt at JacksonAnnotationIntrospector
public class MyAnnotationIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(AnnotatedMember annotatedMember) {
//need this part
}
}
And the implementation of ObjectMapper is
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
like this
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD})
public #interface MyAnnotation {
}
public class Vehicle {
private String vehicle_name;
#MyAnnotation
private String vehicle_model;
//getters and setters
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
if (!System.getenv("var").equals("true")) {
return false;
}
if(_findAnnotation(m, MyAnnotation.class) != null){
return true;
} else {
return false;
}
});
Vehicle vehicle = new Vehicle();
vehicle.setVehicle_model("vehicle_model_value");
vehicle.setVehicle_name("vehicle_name_value");
String value = objectMapper.writeValueAsString(vehicle);
System.out.println(value);
}
}
I'm wondering if there is any legit way to dynamically allocate name of JsonProperty so I would change it over time when needed ? With that being said I mean having :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record
{
public String Name;
#JsonIgnoreProperties(ignoreUnknown = true)
public static class QueryResult<T>
{
public List<T> records;
}
public static class QueryResultRecord extends QueryResult<Record>
{
}
}
Like above, I have a property Name, which by default will be named "Name" like this:
[
{
Name: "Test",
},
{
Name: "test",
},
]
Even though I have flexibility to use #JsonProperty("name") that's not a solution. What I am after is changing it multiple times when needed as I have some parameterized query which relies on it. So I would like to have Name, FirstName, LastName and so on. Is refletion api the right thing to use it here ?
The easiest legit way is to write custom AnnotationIntrospector:
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class MyJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector
{
private static final long serialVersionUID = 1L;
#Override
public PropertyName findNameForSerialization(Annotated a) {
PropertyName pn = super.findNameForSerialization(a);
if (pn.getSimpleName().equals("Name")) {
return pn.withSimpleName("LastName"); // set property name to your heart's content...
}
return pn;
}
}
and then pass it to the jackson mapper:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new MyJacksonAnnotationIntrospector());
Record r1 = new Record();
mapper.writeValue(System.out, r1);
Note: the same introspector is used during deserialization.
I didn't found any simple way to do it but you can use a custom JsonSerializer and implement your logic in it :
// Record class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record {
protected String name;
public Record(String name) {
this.name = Name;
}
// ...
}
// RecordJsonSerializer class
public static class RecordJsonSerializer extends JsonSerializer<Record> {
private static final String[] NAMES = new String[]{
"Name",
"FirstName"
// ...
};
protected int idx;
public RecordJsonSerializer() {
idx = 0;
}
#Override
public void serialize(Record r, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException {
jg.writeStartObject();
jg.writeObjectField(NAMES[idx++], r.name); // Change the field name
jg.writeEndObject();
}
}
// Use case
Record[] records = new Record[]{
new Record("r0"),
new Record("r1")
};
ObjectMapper mapper = new ObjectMapper()
.registerModule(
new SimpleModule("Record")
.addSerializer(Record.class, new RecordJsonSerializer())); // Register the serializer instance
System.out.println(mapper.writeValueAsString(records));
The output of this is: [{"Name":"r0"},{"FirstName":"r1"}]
Of course you must change the logic to define the property name to use when serializing the object (mine will crash with 3 records but it's just a simple example).
I have trouble with deserialization JSON to some of classes ChildA, ChildB and etc. that implements Basic interface in following example.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = InstagramUser.class, name = "ChildA")
})
public interface Basic {
getName();
getCount();
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildA")
public class ChildA implements Basic { ... }
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildB")
public class ChildB implements Basic { ... }
...
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response<E extends Basic> {
#JsonProperty("data")
private List<E> data;
public List<E> getData() {
return data;
}
public void setData(List<E> data) {
this.data = data;
}
}
// deserialization
HTTPClient.objectMapper.readValue(
response,
(Class<Response<ChildA>>)(Class<?>) Response.class
)
Exception is: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'type' that is to contain type id (for class Basic)
Expected JSON is like this:
{
"data": [{ ... }, ...]
}
There is no property that is presented in all type objects so they are completely different. But as you can see on readValue line I know what is expected type. How to structure JsonTypeInfo and JsonSubTypes annotaions to deserialize JSON as expected class?
I kinda had the same problem as you, based in the reading here: Jackson Deserialize Abstract Classes I created my own solution, it basically consists of creating my own deserializer, the trick is to use/identify a specific property within JSON to know which instance type should be returned from deserialization, example is:
public interface Basic {
}
First Child:
public class ChildA implements Basic {
private String propertyUniqueForThisClass;
//constructor, getters and setters ommited
}
SecondChild:
public class ChildB implements Basic {
private String childBUniqueProperty;
//constructor, getters and setters ommited
}
The deserializer (BasicDeserializer.java) would be like:
public class BasicDeserializer extends StdDeserializer<Basic> {
public BasicDeserializer() {
this(null);
}
public BasicDeserializer(final Class<?> vc) {
super(vc);
}
#Override
public Basic deserialize(final JsonParser jsonParser,
final DeserializationContext deserializationContext)
throws IOException {
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
// look for propertyUniqueForThisClass property to ensure the message is of type ChildA
if (node.has("propertyUniqueForThisClass")) {
return mapper.treeToValue(node, ChildA.class);
// look for childBUniqueProperty property to ensure the message is of type ChildB
} else if (node.has("childBUniqueProperty")) {
return mapper.treeToValue(node, ChildB.class);
} else {
throw new UnsupportedOperationException(
"Not supported class type for Message implementation");
}
}
}
Finally, you'd have an utility class (BasicUtils.java):
private static final ObjectMapper MAPPER;
// following good software practices, utils can not have constructors
private BasicUtils() {}
static {
final SimpleModule module = new SimpleModule();
MAPPER = new ObjectMapper();
module.addDeserializer(Basic.class, new BasicDeserializer());
MAPPER.registerModule(module);
}
public static String buildJSONFromMessage(final Basic message)
throws JsonProcessingException {
return MAPPER.writeValueAsString(message);
}
public static Basic buildMessageFromJSON(final String jsonMessage)
throws IOException {
return MAPPER.readValue(jsonMessage, Basic.class);
}
For testing:
#Test
public void testJsonToChildA() throws IOException {
String message = "{\"propertyUniqueForThisClass\": \"ChildAValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildA);
System.out.println(basic);
}
#Test
public void testJsonToChildB() throws IOException {
String message = "{\"childBUniqueProperty\": \"ChildBValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildB);
System.out.println(basic);
}
The source code can be found on: https://github.com/darkstar-mx/jsondeserializer
I find not exactly solution but a workaround. I used custom response class ChildAResponse and passed it to ObjectMapper.readValue() method.
class ChildAResponse extends Response<ChildA> {}
// deserialization
HTTPClient.objectMapper.readValue(
response,
ChildAResponse.class
)
So JsonTypeInfo and JsonSubTypes annotations on the interface are no longer needed.
I want to use custom JSON deserializer for some classes(Role here) but I can't get it working. The custom deserializer just isn't called.
I use Spring Boot 1.2.
Deserializer:
public class ModelDeserializer extends JsonDeserializer<Role> {
#Override
public Role deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return null; // this is what should be called but it isn't
}
}
Controller:
#RestController
public class RoleController {
#RequestMapping(value = "/role", method = RequestMethod.POST)
public Object createRole(Role role) {
// ... this is called
}
}
#JsonDeserialize on Role
#JsonDeserialize(using = ModelDeserializer.class)
public class Role extends Model {
}
Jackson2ObjectMapperBuilder bean in Java Config
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.deserializerByType(Role.class, new ModelDeserializer());
return builder;
}
What am I doing wrong?
EDIT It is probably caused by #RestController because it works with #Controller...
First of all you don't need to override Jackson2ObjectMapperBuilder to add custom deserializer. This approach should be used when you can't add #JsonDeserialize annotation. You should use #JsonDeserialize or override Jackson2ObjectMapperBuilder.
What is missed is the #RequestBody annotation:
#RestController
public class JacksonCustomDesRestEndpoint {
#RequestMapping(value = "/role", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Object createRole(#RequestBody Role role) {
return role;
}
}
#JsonDeserialize(using = RoleDeserializer.class)
public class Role {
// ......
}
public class RoleDeserializer extends JsonDeserializer<Role> {
#Override
public Role deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// .................
return something;
}
}
There is also another pretty interesting solution which can be helpful in case when you want to modify your JSON body before calling default deserializer. And let's imagine that you need to use some additional bean for that (use #Autowire mechanism)
Let's image situation, that you have the following controller:
#RequestMapping(value = "/order/product", method = POST)
public <T extends OrderProductInterface> RestGenericResponse orderProduct(#RequestBody #Valid T data) {
orderService.orderProduct(data);
return generateResponse();
}
Where OrderProductInterface is:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonSerialize(include = NON_EMPTY)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, visible = true, property = "providerType")
#JsonSubTypes({
#JsonSubTypes.Type(value = OrderProductForARequestData.class, name = "A")
})
public interface OrderProductInterface{}
The code above will provide dynamic deserialization base on filed providerType and validation according to concrete implementation. For better grasp, consider that OrderProductForARequestData can be something like that:
public class OrderProductForARequestData implements OrderProductInterface {
#NotBlank(message = "is mandatory field.")
#Getter #Setter
private String providerId;
#NotBlank(message = "is mandatory field.")
#Getter #Setter
private String providerType;
#NotBlank(message = "is mandatory field.")
#Getter #Setter
private String productToOrder;
}
And let's image now that we want to init somehow providerType (enrich input) before default deserialization will be executed. so the object will be deserialized properly according to the rule in OrderProductInterface.
To do that you can just modify your #Configuration class in the following way:
//here can be any annotation which will enable MVC/Boot
#Configuration
public class YourConfiguration{
#Autowired
private ObjectMapper mapper;
#Autowired
private ProviderService providerService;
#Override
public void setup() {
super.setup();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == OrderProductInterface.class) {
return new OrderProductInterfaceDeserializer(providerService, beanDesc);
}
return deserializer;
}
});
mapper.registerModule(module);
}
public static class OrderProductInterfaceDeserializer extends AbstractDeserializer {
private static final long serialVersionUID = 7923585097068641765L;
private final ProviderService providerService;
OrderProductInterfaceDeserializer(roviderService providerService, BeanDescription beanDescription) {
super(beanDescription);
this.providerService = providerService;
}
#Override
public Object deserializeWithType(JsonParser p, DeserializationContext context, TypeDeserializer typeDeserializer) throws IOException {
ObjectCodec oc = p.getCodec();
JsonNode node = oc.readTree(p);
//Let's image that we have some identifier for provider type and we want to detect it
JsonNode tmp = node.get("providerId");
Assert.notNull(tmp, "'providerId' is mandatory field");
String providerId = tmp.textValue();
Assert.hasText(providerId, "'providerId' can't be empty");
// Modify node
((ObjectNode) node).put("providerType",providerService.getProvider(providerId));
JsonFactory jsonFactory = new JsonFactory();
JsonParser newParser = jsonFactory.createParser(node.toString());
newParser.nextToken();
return super.deserializeWithType(newParser, context, typeDeserializer);
}
}
}