Jackson serialization: Wrap fields - java

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":"..."}}

Related

Jackson preprocess json before sending to deserialization

I have a JSON string which I would like to translate into POJO using ObjectMapper.readValue method.
The thing is that the input Json string contains keys which I would like to filter out before the deserialization.
I came across DelegatingDeserialization class which according to my understanding allows you to extend it and override one of the deserialize method to reconstruct the json input and then pass it on the chain.
The thing is that I try to enable this custom delegating deserializer by adding the
#JsonDeserialize(using = CustomDelegatingDeserialization.class) on top of my Pojo - is that the right way to instantiate it??
Here is a snippet of my custom delegator:
public static class CustomDeserializer extends DelegatingDeserializer {
public CustomDeserializer() {
super(null);
}
public CustomDeserializer(JsonDeserializer<?> defaultDeserializer) {
super(defaultDeserializer);
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new CustomDeserializer(newDelegatee);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(restructure(p), ctxt);
}
private JsonParser restructure(JsonParser jp) throws IOException {
...
return newJsonParser;
}
}
Am I taking the right path or there is a more fitting solution??
THank you!
EDIT 1
Another approach is to have a CustomJsonDeserializer extends JsonDeserializer<T> and override its deserialize method then reconstruct the Node and propagate it by returning codec.treeToValue(jsonNode, Pojo.class); this makes sense BUT it gets me into infinite loop! any idea why?
Assuming your POJO doesn't have a property that you would like to ignore you can use annotation #JsonIgnoreProperties(ignoreUnknown = true)for your class. That tells Jeckson to ignore properties that are not present in your POJO. Read more on the issue how to ignore some properties here: Jackson Unmarshalling JSON with Unknown Properties

Serialize objects to a map with the object class as the key?

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();
}
}

Generic POJO to JSON using jackson with dynamic json key as per Generic class

I have a POJO like:
class Wrapper,T> {
private int count;
private T data;
// getters setters
}
While converting it to JSON using Jackson, json is something like:
{
count:1,
**data**:{}
}
I need the data key to be changed as per class name T or some other value related to class name, how can I achieve this. Please suggest.
Thankyou.
Using a custom serializer, you can do anything you want since you have complete control over the serialization process. See for example https://www.baeldung.com/jackson-custom-serialization.
Your serialize method would look something like this:
#Override
public void serialize(
Wrapper<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String derivedName = value.getClass().getSimpleName() + "Data"; // just an example
jgen.writeStartObject();
jgen.writeNumberField("count", value.getCount());
jgen.writeObjectField(derivedName, value.getData());
jgen.writeEndObject();
}

Is there any way to ignore JsonProcessingException without breaking deserialization process

I'm looking into a solution for Jackson deserialization JSON to an instance of a class without breaking the whole process, currently when I do something like:
If Actor.class was like:
#JsonInclude(JsonInclude.Include.NON_EMPTY)
#JsonPropertyOrder(alphabetic = true)
public abstract class BaseDTO {
}
public class Character extend BaseDTO {
private LocalDateTime updatedDate;
private String name;
// Setters and getters
}
and deserialize json {"updatedDate":"N/A", "name": "Jon Snow"} like:
String json = "{\"updatedDate\":\"N/A\", \"name\": \"Jon Snow\"}";
ObjectMapper mapper = new ObjectMapper();
final Character character = mapper.reader().forType(Character.class).readValue(json);
Or as Play Framework directly:
final Character character = Json.fromJson(json, Character.class);
I definitely will get an exception like:
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDateTime` from String "N/A": Text 'N/A' could not be parsed at index 0
And InvalidFormatException actually is a JsonProcessingException, there is also MismatchedInputException and other exceptions, so I need somehow gracefully continue with processing and get the object character and have name value at least instead of stopping it at all.
I prefer to:
Use annotations to config the parser or any solution to be applied to BaseDTO.
Logging the issue in the log file so I know that something wrong happened.
I really can't find the way right now without a huge effort, so I wonder if there is any out-of-box solution do that without re-invent the wheel.
I am assuming that you wannt ot consider string like N/A as null. Using a custom deserializer it can be achieved like this.
class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext context) throws IOException {
if ("N/A".equals(p.getText())) return null;
return LocalDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(p.getText()));
}
}
Hope this helps!

custom field serialization using jacson

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.

Categories

Resources