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();
}
}
Related
I have the following code with Jackson:
public class Header implements Serializable {
#JsonProperty("numeroUnico")
private Integer numeroCliente;
#JsonProperty("oficina")
private Integer oficina;
#JsonProperty("fecha")
#JsonSerialize(using = CustomDateSerializer.class)
private Date fechaInscripcion;
}
this is my class "CustomDateSerializer.class"
public class CustomDateSerializer extends StdSerializer<Date> {
private SimpleDateFormat formatter
= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
public CustomDateSerializer() {
this(null);
}
public CustomDateSerializer(Class t) {
super(t);
}
#Override
public void serialize (Date value, JsonGenerator gen, SerializerProvider arg2)
throws IOException, JsonProcessingException {
gen.writeString(formatter.format(value));
}
}
They asked me to migrate all the implementations of Jackson to Gson.
Taking into account that the notation in Jackson #JsonProperty has an equivalence in Gson that is #SerializedName.
But for the notation in Jackson of:
#JsonSerialize (using = CustomDateSerializer.class)
What is its equivalent for Gson? if not, as it should be the implementation for attributes of type Date in my DTO.
I think the closest and probably the only match is #TypeAdapter. However you need to code either JsonSerializer<T> or TypeAdapter<T> to be used with that annotation.
For example how to make something like your CustomDateSerializer see accepted answer for this question.
Or maybe you can wrap your existing CustomDateSerializer with Gson TypeAdapter<Date> and use that in the annotation.
I have found how to customize ObjectMapper date format in order to let Spring to help to auto serialize/deserialize (serialize when I want to return object to client, deserialize when the request body is json object), but I have lot of DTO with different date format, some might need yyyy-mm-dd, some is dd-mm-yyyy, one ObjectMapper will not work for different required date format, what is the best practice solution for this issue?
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(mappingJacksonHttpMessageConverter());
}
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() {
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy"));
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper);
mappingJacksonHttpMessageConverter.setPrettyPrint(true);
return mappingJacksonHttpMessageConverter;
}
You could use custom Serializers and handle the different formats within a single Serializer. Here are a few pages that have some info on how to create custom Serializer/Deserializers:
Create Custom Serializer
Create Custom Deserializer
-- Edit --
From the documentation for MappingJacksonHttpMessageConverter (some emphasis added):
setObjectMapper
public void setObjectMapper(org.codehaus.jackson.map.ObjectMapper objectMapper)
Set the ObjectMapper for this view. If not set, a default ObjectMapper is used.
Setting a custom-configured ObjectMapper is one way to take further control
of the JSON serialization process. For example, an extended SerializerFactory
can be configured that provides custom serializers for specific types.
The other option for refining the serialization process is to use Jackson's
provided annotations on the types to be serialized, in which case a
custom-configured ObjectMapper is unnecessary.
This means that you do not even need to call setObjectMapper if you have Serializers/Deserializers defined by annotations (as described in the links I posted above). For your benefit, here is an example:
For Serializing:
Create a StdSerializer object to handle the type you are interested in
public class ItemSerializer extends StdSerializer<Item> {
// ...
#Override
public void serialize(Item value, JsonGenerator jgen, SerializerProvider provider) {
// Write the Item data into the JsonGenerator
}
}
Define the Serializer for the object via annotations
#JsonSerialize(using = ItemSerializer.class)
public class Item {
// ...
}
For Deserialization
Create a StdDeserializer object to handle the type you are interested in
public class ItemDeserializer extends StdDeserializer<Item> {
// ...
#Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// Handle the different date formats here!
return new Item(/*parsed date object*/);
}
}
Define the Deserializer for the object via annotations
#JsonDeserialize(using = ItemDeserializer.class)
public class Item {
// ...
}
I'm trying to Json Serialize a POJO with MonetaryAmount field as a string, but the resulting output does not follow the prescribed shape format.
// org.javamoney:moneta:1.1
// com.fasterxml.jackson.core:jackson-annotations:2.7.0
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"CBNOT_CHARGEBACK_AMOUNT"
})
public class TestMonetaryAmountJsonSerialization {
#JsonProperty("CBNOT_CHARGEBACK_AMOUNT")
#NotNull
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "¤#,##0.00", locale = "en_US")
private final MonetaryAmount chargebackAmount = Monetary.getDefaultAmountFactory().setCurrency("USD").setNumber(12.50).create();
private static final ObjectMapper mapper = new ObjectMapper();
#Test
public void testThis() throws JsonProcessingException{
String json = mapper.writeValueAsString(this);
System.out.println(json);
Assert.assertEquals("{\"CBNOT_CHARGEBACK_AMOUNT\":\"¤12.50\"}",json);
}
}
OUTPUT: {"CBNOT_CHARGEBACK_AMOUNT":{"currency":{"context":{"empty":false,"providerName":"java.util.Currency"},"defaultFractionDigits":2,"currencyCode":"USD","numericCode":840},"number":12.5,"factory":{"defaultMonetaryContext":{"precision":0,"fixedScale":false,"amountType":"org.javamoney.moneta.Money","maxScale":63,"empty":false,"providerName":null},"maxNumber":null,"minNumber":null,"amountType":"org.javamoney.moneta.Money","maximalMonetaryContext":{"precision":0,"fixedScale":false,"amountType":"org.javamoney.moneta.Money","maxScale":-1,"empty":false,"providerName":null}},"context":{"precision":0,"fixedScale":false,"amountType":"org.javamoney.moneta.Money","maxScale":63,"empty":false,"providerName":null},"numberStripped":12.5,"zero":false,"negative":false,"negativeOrZero":false,"positive":true,"positiveOrZero":true}}
Any ideas what am I doing wrong? I threw the kitchen sink in this code here, only for illustrative purposes and compact presentation.
JsonFormat is an annotation used in several (de)serializers defined by Jackson (e.g. DateTimeSerializerBase, NumberSerializers.Base and some other, full list here), it's not a general purpose mechanism turning any object into a string:
Unlike most other Jackson annotations, annotation does not have
specific universal interpretation: instead, effect depends on datatype
of property being annotated (or more specifically, deserializer and
serializer being used).
Specifying it won't have any effect unless you create a custom serializer for MonetaryAmount or use one that makes use of this annotation (and also its pattern property), but if you create a custom serializer, chances are you won't need that level of flexibility as to specify different patterns for different fields and could just use a fixed MonetaryAmountFormat or build the necessary string from the MonetaryAmount object otherwise.
For example
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"CBNOT_CHARGEBACK_AMOUNT"
})
public class TestMonetaryAmountJsonSerialization {
#JsonProperty("CBNOT_CHARGEBACK_AMOUNT")
#NotNull
private final MonetaryAmount chargebackAmount = Monetary.getDefaultAmountFactory().setCurrency("USD").setNumber(12.50).create();
private static final ObjectMapper mapper = new ObjectMapper();
static {
SimpleModule monetaryModule = new SimpleModule();
monetaryModule.addSerializer(MonetaryAmount.class, new MonetaryAmountSerializer());
mapper.registerModule(monetaryModule);
}
#Test
public void testThis() throws JsonProcessingException {
String json = mapper.writeValueAsString(this);
System.out.println(json);
Assert.assertEquals("{\"CBNOT_CHARGEBACK_AMOUNT\":\"$12.50\"}", json);
}
public static class MonetaryAmountSerializer extends JsonSerializer<MonetaryAmount> {
public void serialize(MonetaryAmount monetaryAmount,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
StringBuilder sb = new StringBuilder();
MonetaryAmountDecimalFormatBuilder
.of("¤#,##0.00").withCurrencyUnit(monetaryAmount.getCurrency()).build()
.print(sb, monetaryAmount);
jsonGenerator.writeString(sb.toString());
}
}
}
In Spring MVC project, I have controllers which return data in the form of various objects. Later these objects are serialized to JSON, and JSON returned as a response to a client.
This is achieved by registering custom MappingJackson2HttpMessageConverter with configureMessageConverters() in application config. The converter uses
jackson.databind.ObjectWriter.writeValue(jackson.core.JsonGenerator, object)
for object serialization.
Now I need to implement XSS protection. Since all data goes through the step of serialization, I want to filter here String values, removing all symbols except those in whitelist (alphanumeric and some punctuation signs).
Is there a way to provide Jackson with a filter which will edit String values during serialization?
I have an instance of MappingJackson2HttpMessageConverter. There, in method writeInternal(Object object, HttpOutputMessage outputMessage), I create an instance of my custom mapper: mapper = new KJsonMapper(); This mapper is then used there to generate JSON.
KJsonMapper.java:
public class KJsonMapper extends ObjectMapper {
public KJsonMapper() {
enableAntiXSS();
}
private void enableAntiXSS() {
SimpleModule module = new SimpleModule("Anti-XSS Serializer",
new Version(1, 0, 0, "FINAL", "klab", "klab.anti-xss-serializer"));
module.addSerializer(String.class, new KJsonAntiXssSerializer());
registerModule(module);
}
}
The mapper itself uses custom JsonSerializer, which is to provide anti-xss filtering.
KJsonAntiXssSerializer.java:
public class KJsonAntiXssSerializer extends JsonSerializer<String> {
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
if (value == null) {
return;
}
String encodedValue = antiXss(value);
jsonGenerator.writeString(encodedValue);
}
private String antiXss(String value) {
// return processed value from here
}
}
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":"..."}}