JsonNode toString() is failing after adding custom serializer for Instant - java

after adding a custom serializer for Instant toString is failing on JsonNode.
I have the below configuration for ObjectMapper.
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.registerModule(new SimpleModule());
MAPPER.registerModule(new ParameterNamesModule());
MAPPER.registerModule(new Jdk8Module());
MAPPER.registerModule(new JavaTimeModule());
SimpleModule module = new SimpleModule();
module.addSerializer(Instant.class, new InstantSerializer());
module.addDeserializer(Instant.class,new InstantDeSerializer());
MAPPER.registerModule(module);
MAPPER.setVisibility(MAPPER.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(ANY)
.withGetterVisibility(NONE)
.withSetterVisibility(NONE)
.withCreatorVisibility(ANY));
MAPPER.disable(FAIL_ON_EMPTY_BEANS);
MAPPER.disable(FAIL_ON_UNKNOWN_PROPERTIES);
MAPPER.disable(WRITE_DATES_AS_TIMESTAMPS);
MAPPER.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
}
Below are Serializer and De-serializer for Instant.
public class InstantSerializer extends StdSerializer<Instant> {
public InstantSerializer() {
this(null);
}
public InstantSerializer(Class<Instant> t) {
super(t);
}
#Override
public void serialize(
Instant value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeEmbeddedObject(value);
}
}
public class InstantDeSerializer extends StdDeserializer<Instant> {
public InstantDeSerializer() {
this(null);
}
protected InstantDeSerializer(final Class<?> vc) {
super(vc);
}
#Override
public Instant deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JacksonException {
if (p.currentToken() == JsonToken.VALUE_EMBEDDED_OBJECT) {
return (Instant) p.getEmbeddedObject();
}
else if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) {
return Instant.ofEpochMilli(p.getLongValue());
}
else if (p.currentToken() == JsonToken.VALUE_STRING) {
return Instant.parse(p.getValueAsString());
}
throw new RuntimeException();
}
}
I have below class
public class A{
private Instant time;
public Instant getTime() {
return time;
}
public void setTime(final Instant time) {
this.time = time;
}
}
below is the test
#Test
public void test() {
A a = new A();
a.setTime(Instant.now());
final JsonNode jsonNode = MAPPER.readTree(a);
System.out.println(jsonNode);
}
I get the below exception though JavaTimeModule is registered. could someone help me Thanks in advance
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.Instant` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1276)
at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:1118)
at com.fasterxml.jackson.databind.node.POJONode.serialize(POJONode.java:115)
at com.fasterxml.jackson.databind.node.ObjectNode.serialize(ObjectNode.java:328)
at com.fasterxml.jackson.databind.ser.std.SerializableSerializer.serialize(SerializableSerializer.java:39)
at com.fasterxml.jackson.databind.ser.std.SerializableSerializer.serialize(SerializableSerializer.java:20)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1514)
at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1215)
at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1085)
at com.fasterxml.jackson.databind.node.InternalNodeMapper.nodeToString(InternalNodeMapper.java:30)
... 92 more

Related

Throw error if strings are not double quoted while using jackson objectmapper deserialization

I have a JSON:
{
"stringField" : 1234,
"booleanField": true,
"numberField": 1200.00
}
I use object mapper to deserialize the json into:-
#Data
class SomeClass {
String stringField;
boolean booleanField;
float numberField;
}
I would like the objectMapper to throw an error because, the values for String fields must be double quoted according to the json spec. How can i get objectMapper to throw an error?
You can write custom string deserializer.(i assume you are using spring)
#Configuration
public class JacksonConfiguration {
#Bean
SimpleModule jacksonDeserializerConfiguration() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StdDeserializer<String>(String.class) {
#Override
public String deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
if (!parser.hasToken(JsonToken.VALUE_STRING)) {
//throw ex what do u want
throw new RuntimeException("String not include quote");
}
return StringDeserializer.instance.deserialize(parser, context);
}
});
return module;
}
}
This should fix your issue.
class SomeClass {
#JsonDeserialize(using=ForceStringDeserializer.class)
public String stringField;
public boolean booleanField;
public float numberField;
}
class ForceStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
throw deserializationContext.wrongTokenException(jsonParser, JsonToken.VALUE_STRING, "Attempted to parse Integer to String but this is forbidden");
}
return jsonParser.getValueAsString();
}
}
You just need to setup jackson objectmapper like this
JsonFactory factory = new JsonFactory();
factory.disable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
ObjectMapper mapper = new ObjectMapper(factory)
This should throw error during serialization/deserilaization

Java Jackson serializer including FQCN

I'm trying to create a generic Jackon polymorphic serializer that is able to serialize and deserialize to and from JSON with this format including the fqcn of the class of the object:
{
"fqcn": "full qualified class name of the object",
"data": "serialized object"
}
This wrapper should be applied to any object, so for example this will be the JSON representation of a HashMap> object:
{
"fqcn": "java.util.HashMap",
"data": {
"key1": {
"fqcn": "java.util.ArrayList",
"data": [
{
"fqcn": "java.lang.String",
"data": "value1"
},
{
"fqcn": "java.lang.String",
"data": "value2"
}
]
},
"key2": {
...
}
}
}
I could use a MixIn annotation all objects with #JsonTypeInfo
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT)
public interface ObjMixin {
}
---
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Object.class, ObjMixin.class);
However, the format does not match with the required format: {"fqcn": ..., "data": ...}
I've also tried to register a StdConverter to convert any objects to a wrapper like this:
public class ObjectWrapper {
private String fqcn;
private Object data;
public ObjectWrapper(Object obj) {
this.fqcn = obj.getClass.getCanonicalName();
this.data = obj;
}
}
However it is not possible to create a StdDelegatingSerializer for Object.class.
With a custom StdSerializer like the following I am getting StackOverflowError:
#Override
public void serialize(Object obj, JsonGenerator jsonGen, SerializerProvider serializerProvider) throws IOException {
jsonGen.writeStartObject();
jsonGen.writeStringField("fqcn", obj.getClass().getCanonicalName());
jsonGen.writeFieldName("data");
if (obj instanceof Iterable) {
jsonGen.writeStartArray();
// Recursive serialization of all elements in the iterable
jsonGen.writeEndArray();
} else if (obj instanceof Map) {
jsonGen.writeStartObject();
// Recursive serialization of all elements in the map
jsonGen.writeEndObject();
} else {
// Infinite recursion here because I'm defining this serializer for Object.class
serializerProvider.defaultSerializeValue(obj, jsonGen);
}
}
Does anyone know any other solution to be able to achieve this?
You could use a custom serializer and custom serializer provider to wrap every object you want to serialize into this wrapper object (EDIT: that did not work recusrively, updated the code to not use the wrapper object but write the fields instead):
public class FQCNTest {
#Test
public void doTest() throws JsonProcessingException {
final ObjectMapper om = getObjectMapper();
final Object obj = getTestObject();
final String json = om.writeValueAsString(obj);
System.out.println(json); // {"fqcn":"java.util.HashMap","data":{"k":{"fqcn":"java.lang.String","data":"v"}}}
final Object obj2 = getTestValue();
final String json2 = om.writeValueAsString(obj2);
System.out.println(json2); // {"fcqn":"java.lang.String","data":"hello"}
final Object obj3 = null;
final String json3 = om.writeValueAsString(obj3);
System.out.println(json3); // null
}
private ObjectMapper getObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
final SerializerProvider sp = mapper.getSerializerProviderInstance();
mapper.setSerializerProvider(new CustomSerializerProvider(sp, mapper.getSerializerFactory()));
return mapper;
}
private Object getTestObject() {
final HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("k", "v");
return hashMap;
}
private Object getTestValue() {
return "hello";
}
}
class CustomSerializerProvider extends DefaultSerializerProvider {
private final SerializerProvider defaultInstance;
protected CustomSerializerProvider(final SerializerProvider defaultInstance, final SerializerFactory f) {
super(defaultInstance, defaultInstance.getConfig(), f);
this.defaultInstance = defaultInstance;
}
#Override
public WritableObjectId findObjectId(final Object forPojo, final ObjectIdGenerator<?> generatorType) {
return defaultInstance.findObjectId(forPojo, generatorType);
}
#Override
public JsonSerializer<Object> serializerInstance(final Annotated annotated, final Object serDef) throws JsonMappingException {
return new CustomSerializer();
}
#Override
public Object includeFilterInstance(final BeanPropertyDefinition forProperty, final Class<?> filterClass) {
try {
return defaultInstance.includeFilterInstance(forProperty, filterClass);
} catch (final JsonMappingException e) {
throw new RuntimeException(e);
}
}
#Override
public boolean includeFilterSuppressNulls(final Object filter) throws JsonMappingException {
return defaultInstance.includeFilterSuppressNulls(filter);
}
#Override
public DefaultSerializerProvider createInstance(final SerializationConfig config, final SerializerFactory jsf) {
return this;
}
#Override
public void serializeValue(final JsonGenerator gen, final Object value) throws IOException {
new CustomSerializer().serialize(value, gen, this);
}
#Override
public void serializeValue(final JsonGenerator gen, final Object value, final JavaType rootType) throws IOException {
super.serializeValue(gen, value, rootType);
}
#Override
public void serializeValue(final JsonGenerator gen, final Object value, final JavaType rootType, final JsonSerializer<Object> ser) throws IOException {
super.serializeValue(gen, value, rootType, ser);
}
}
class CustomSerializer extends StdSerializer<Object> {
protected CustomSerializer() {
super(Object.class);
}
#Override
public void serialize(final Object value, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
if (value == null) {
provider.defaultSerializeValue(value, gen);
return;
}
final Class<?> clazz = value.getClass();
final JsonSerializer<Object> serForClazz = provider.findValueSerializer(clazz);
gen.writeStartObject();
gen.writeStringField("fqcn", clazz.getCanonicalName());
gen.writeFieldName("data");
if (value instanceof Iterable) {
gen.writeStartArray();
for (final Object e : ((Iterable<?>) value)) {
final JsonSerializer<Object> ser = new CustomSerializer();
ser.serialize(e, gen, provider);
}
gen.writeEndArray();
} else if (value instanceof Map) {
gen.writeStartObject();
// Recursive serialization of all elements in the map
for (final Map.Entry<?, ?> e : ((Map<?, ?>) value).entrySet()) {
final String key = e.getKey().toString(); // need to handle keys better
final Object mapValue = e.getValue();
gen.writeFieldName(key);
final JsonSerializer<Object> ser = new CustomSerializer();
ser.serialize(mapValue, gen, provider);
}
gen.writeEndObject();
} else {
serForClazz.serialize(value, gen, provider);
}
gen.writeEndObject();
}
}
Note: this code may contain too much stuff that is not necessary, I just took it far enough to make it work for the specific example. (and did not test deserialization, that may be a totally different thing)

Custom deserialization with Jackson: extend default deserializer

I would like to make my own deserializer by extending the default one an setting some more values after it:
simplified code:
public class Dto {
public String originalJsonString;
}
public MyFooDto extends Dto {
public String myField;
}
#Bean
public ObjectMapper deserializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(MyFooDto.class, new JsonDtoDeserializer<>());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
// or maybe instead of the Beam just #JsonDeserialize(using = JsonDtoDeserializer.class) before MyFooDto?
public class JsonDtoDeserializer<T extends Dto> extends StdDeserializer<T> {
// or maybe extends JsonDeserializer? or UntypedObjectDeserializer? or UntypedObjectDeserializer.Vanilla?
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// here I would like:
T item = super.deserialize"AsUsual"(p, ctxt);
// the difficulty is to avoid the loop of death, where the deserializer would call itself for the eternity...
// And then set other Dto-fields depending on the original Json input, for example:
item.originalJsonString = p.readValueAsTree().toString();
return item;
}
}
As you can see, I would also like to reuse this Dto mother class for other DTOs.
I didn't find any example of it. I am really the first one in the world?
what should be the deserialize"AsUsual"(p, ctxt)?
what motherclass should I use? JsonDeserializer / StdDeserializer / UntypedObjectDeserializer?
Will the deserializer know which class of T it has to instantiate?
Thank you Community!
As Sharon said (based on How do I call the default deserializer from a custom deserializer in Jackson)
#Bean
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (Dto.class.isAssignableFrom(beanDesc.getBeanClass())) {
return new JsonDtoDeserializer<>(deserializer, beanDesc.getBeanClass());
}
return deserializer;
}
});
objectMapper.registerModule(simpleModule);
return objectMapper;
}
public class JsonDtoDeserializer<T extends Dto> extends StdDeserializer<T> implements ResolvableDeserializer /*StdDeserializer<Dto<T>>*/ /*UntypedObjectDeserializer.Vanilla*/ /*<T>*/ /*implements ResolvableDeserializer*/ {
private final JsonDeserializer<?> defaultDeserializer;
public JsonDtoDeserializer(JsonDeserializer<?> defaultDeserializer, Class<?> clazz) {
super(clazz);
this.defaultDeserializer = defaultDeserializer;
}
#Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
#SuppressWarnings("unchecked")
T itemObj = (T) defaultDeserializer.deserialize(p, ctxt);
return itemObj;
}
// for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
// otherwise deserializing throws JsonMappingException??
#Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
{
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}

Restructure JSON before deserializing with Jackson

We have a service which currently consumes JSON. We want to slightly restructure this JSON (move one property one level up) but also implement graceful migration so that our service could process old structure as well as new structure. We're using Jackson for JSON deserialization.
How do we restructure JSON prior to deserialization with Jackson?
Here's a MCVE.
Assume our old JSON looks as follows:
{"reference": {"number" : "one", "startDate" : [2016, 11, 16], "serviceId" : "0815"}}
We want to move serviceId one level up:
{"reference": {"number" : "one", "startDate" : [2016, 11, 16]}, "serviceId" : "0815"}
This are the classes we want to deserialize from both old an new JSONs:
public final static class Container {
public final Reference reference;
public final String serviceId;
#JsonCreator
public Container(#JsonProperty("reference") Reference reference, #JsonProperty("serviceId") String serviceId) {
this.reference = reference;
this.serviceId = serviceId;
}
}
public final static class Reference {
public final String number;
public final LocalDate startDate;
#JsonCreator
public Reference(#JsonProperty("number") String number, #JsonProperty("startDate") LocalDate startDate) {
this.number = number;
this.startDate = startDate;
}
}
We only want serviceId in Container, not in both classes.
What I've got working is the following deserializer:
public static class ServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> {
private final ObjectMapper objectMapper;
{
objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
#Override
public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectNode node = p.readValueAsTree();
migrate(node);
return objectMapper.treeToValue(node, Container.class);
}
private void migrate(ObjectNode containerNode) {
TreeNode referenceNode = containerNode.get("reference");
if (referenceNode != null && referenceNode.isObject()) {
TreeNode serviceIdNode = containerNode.get("serviceId");
if (serviceIdNode == null) {
TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
}
}
}
}
}
This deserializer first retrieves the tree, manipulates it and then deserializers it using an own instance of ObjectMapper. It works but we really dislike the fact that we have another instance of ObjectMapper here. If we don't create it and somehow use the system-wide instance of ObjectMapper we get an infinite cycle because when we try to call objectMapper.treeToValue, our deserializer gets called recursively. So this works (with an own instance of ObjectMapper) but it is not an optimal solution.
Another method I've tried was using a BeanDeserializerModifier and a own JsonDeserializer which "wraps" the default serializer:
public static class ServiceIdMigrationBeanDeserializerModifier extends BeanDeserializerModifier {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
JsonDeserializer<?> defaultDeserializer) {
if (beanDesc.getBeanClass() == Container.class) {
return new ModifiedServiceIdMigratingContainerDeserializer((JsonDeserializer<Container>) defaultDeserializer);
} else {
return defaultDeserializer;
}
}
}
public static class ModifiedServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> {
private final JsonDeserializer<Container> defaultDeserializer;
public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<Container> defaultDeserializer) {
this.defaultDeserializer = defaultDeserializer;
}
#Override
public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectNode node = p.readValueAsTree();
migrate(node);
return defaultDeserializer.deserialize(new TreeTraversingParser(node, p.getCodec()), ctxt);
}
private void migrate(ObjectNode containerNode) {
TreeNode referenceNode = containerNode.get("reference");
if (referenceNode != null && referenceNode.isObject()) {
TreeNode serviceIdNode = containerNode.get("serviceId");
if (serviceIdNode == null) {
TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
}
}
}
}
}
"Wrapping" a default deserializer seems to be a better approach, but this fails with an NPE:
java.lang.NullPointerException
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:157)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150)
at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:235)
at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:1)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1623)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1217)
at ...
The whole MCVE code is in the following PasteBin. It is a single-class all-containing test case which demonstrates both approaches. The migratesViaDeserializerModifierAndUnmarshalsServiceId fails.
So this leaves me with a question:
How do we restructure JSON prior to deserialization with Jackson?
In the best traditions, right after posting the question, I've managed to solve this.
Two things:
I had to do newJsonParser.nextToken(); to avoid NPE.
Extend DelegatingDeserializer
Here's a working DelegatingDeserializer:
public static class ModifiedServiceIdMigratingContainerDeserializer
extends DelegatingDeserializer {
public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<?> defaultDeserializer) {
super(defaultDeserializer);
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new ModifiedServiceIdMigratingContainerDeserializer(newDelegatee);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(restructure(p), ctxt);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException,
JsonProcessingException {
return super.deserialize(restructure(p), ctxt, intoValue);
}
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException {
return super.deserializeWithType(restructure(jp), ctxt, typeDeserializer);
}
public JsonParser restructure(JsonParser p) throws IOException, JsonParseException {
final ObjectNode node = p.readValueAsTree();
migrate(node);
final TreeTraversingParser newJsonParser = new TreeTraversingParser(node, p.getCodec());
newJsonParser.nextToken();
return newJsonParser;
}
private void migrate(ObjectNode containerNode) {
TreeNode referenceNode = containerNode.get("reference");
if (referenceNode != null && referenceNode.isObject()) {
TreeNode serviceIdNode = containerNode.get("serviceId");
if (serviceIdNode == null) {
TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
}
}
}
}
}

Customize JSON serialization with JaxRS

In a webservice call, I would like to return my objects with this JSON structure.
{
"date" : "30/06/2014",
"price" : {
"val" : "12.50",
"curr" : "EUR"
}
}
I'd like to map this JSON code to this Java structure (with joda-time and joda-money):
public class MyResponse {
LocalDate date;
Money price;
}
My webservice currently looks like this:
#javax.ws.rs.POST
#javax.ws.rs.Path("test")
#javax.ws.rs.Produces({MediaType.APPLICATION_JSON})
#javax.ws.rs.Consumes({MediaType.APPLICATION_JSON})
public MyResponse test(MyRequest request) {
MyResponse response = new MyResponse();
response.setDate(LocalDate.now());
response.setMoney(Money.parse("EUR 12.50"));
return response;
}
So my question is: where do I register a custom handler to format dates as I want as well as money representations?
If you are using Jackson (which should be the default for JBoss EAP 6) you can use custom JsonSerializers
For the LocalDate:
public class DateSerializer extends JsonSerializer<LocalDate> {
#Override
public void serialize(LocalDate date, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(date.toString("dd/MM/yyyy"));
}
}
For the Money:
public class MoneySerializer extends JsonSerializer<Money> {
#Override
public void serialize(Money money, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("val", money.getAmount().toString());
jgen.writeStringField("curr", money.getCurrencyUnit().getCurrencyCode());
jgen.writeEndObject();
}
}
Both Serializers can be registered globally:
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
module.addSerializer(Money.class, new MoneySerializer());
module.addSerializer(LocalDate.class, new DateSerializer());
objectMapper.registerModule(module);
}
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}
For parsing JSON in this custom format you need to implement custom JsonDeserializers.
If you are using Jettison you can do the same thing with custom XmlAdapters.

Categories