Jackson forwarding attribute instead of outer object - java

I have an object that is used as a return type in a REST web service (jax-rs). The object contains a field which is an array of another type. Example:
#Name("PARAMETER_DEFINITION_TABTYPE")
#TableOfDefinition
#XmlType(
name = "parameter_DEFINITION_TABTYPE"
)
class PARAMETER_DEFINITION_TABTYPE {
#XmlElement(
name = "parameter_definition_rectype",
nillable = true
)
public PARAMETER_DEFINITION_RECTYPE[] ELEMENTS;
#Override
public String toString() {
return ELEMENTS == null ? null : java.util.Arrays.toString(ELEMENTS);
}
}
I use all the existing annotations to create my SOAP web services and don't want to touch the class or the existing annotations. The REST service I create uses the same class and generates the following json:
{"parameter_definition_rectype": [
{
"name": "abc"
},
{
"name": "abss"
}
]}
I would like to get the following output (basically ignore the outer element and use only the "ELEMENTS" field):
[
{
"name": "abc"
},
{
"name": "abss"
}
]
I also want to ignore the outer object when the PARAMETER_DEFINITION_TABTYPE is nested in another Object.
Is there a way I can achieve this by using annotations?
Thanks!

You could define a custom serializer and deserializer for PARAMETER_DEFINITION_TABTYPE class which will change the way it's processed by ObjectMapper.
This is explained in the wiki Jackson How-To: Custom Serializers, if you can't add new annotations to PARAMETER_DEFINITION_TABTYPE class you should use a custom module.
It's more or less the below code. The problem here is that you have to provide a lot of custom code to get desired behavior. It would be cleaner to have a separate class to represent the JSON object because it's different than XML object:
public static void main(String[] args) throws Exception {
SimpleModule module = new SimpleModule("WrapperModule", new Version(1, 0, 0, null));
module.addSerializer(Wrapper.class, new WrapperSerializer(Wrapper.class));
module.addDeserializer(Wrapper.class, new WrapperDeserializer(Wrapper.class));
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Wrapper in = new Wrapper();
in.elements = new String[]{"a", "b", "c"};
String json = mapper.writeValueAsString(in);
System.out.println(json);
Wrapper out = mapper.readValue(json, Wrapper.class);
System.out.println(Arrays.toString(out.elements));
}
public static class Wrapper {
public String[] elements;
}
public static class WrapperSerializer extends StdSerializer<Wrapper> {
public WrapperSerializer(Class<Wrapper> t) {
super(t);
}
#Override
public void serialize(Wrapper w, JsonGenerator gen, SerializerProvider provider) throws IOException {
provider.defaultSerializeValue(w.elements, gen);
}
}
public static class WrapperDeserializer extends StdDeserializer<Wrapper> {
public WrapperDeserializer(Class<Wrapper> c) {
super(c);
}
#Override
public Wrapper deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
Wrapper w = new Wrapper();
w.elements = ctx.readValue(p, String[].class);
return w;
}
}

Related

can create custom json string with instantiated data class in java

There is a class defined follows:
#Data // lombok
public class MyData {
#Required // my custom annotation
String testValue1;
Integer testValue2;
}
And myData is instantiated like that:
MyData myData = new MyData();
myData.setTestValue1("test1");
myData.setTestValue2(123);
I want to serialize myData as json string as follows:
{
"testValue1": {
"type": "String",
"isRequired": "true",
"value": "test1"
},
"testValue2": {
"type": "Integer",
"isRequired": "false",
"value": "123"
},
}
Is there a good way to create json string?
edit|
I put quotes on json string that to be able to valid.
I want to set key as field name and create additional field information.
set field type on "type" key and
if field has #Required annotation, set true on "isRequired" and
set instantiated field value on "value".
So I played a bit around with Jackson Serialization and came to this result (certainly unfinished and not fully tested, but works with your given object).:
Module to make Spring / Jackson known of the new Serializer.
#JsonComponent
public class TestSerializerModule extends SimpleModule {
#Override
public String getModuleName() {
return TestSerializerModule.class.getSimpleName();
}
#Override
public Version version() {
return new Version(
1,
0,
0,
"",
TestSerializerModule.class.getPackage().getName(),
"TestModule"
);
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass().equals(MyData.class)) { //Add some smart logic here to identify your objects
return new TestSerializer();
}
return serializer;
}
});
}
}
Then the Serialisier itself:
public class TestSerializer extends StdSerializer<Object> {
protected TestSerializer() {
super(Object.class);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
ClassIntrospector classIntrospector = provider.getConfig().getClassIntrospector();
BasicBeanDescription beanDescription = (BasicBeanDescription) classIntrospector.forSerialization(provider.getConfig(), provider.constructType(value.getClass()), null);
// Start of the MyValue Object
gen.writeStartObject();
beanDescription.findProperties().forEach(p -> {
// Requiered if Annoation is present
boolean required = p.getField().hasAnnotation(Required.class);
try {
// Write all the wanted fields
gen.writeFieldName(p.getName());
gen.writeStartObject();
gen.writeBooleanField("isRequired", required);
gen.writeStringField("type", p.getField().getRawType().getSimpleName());
gen.writeFieldName("value");
Object value1 = p.getGetter().getValue(value);
// Use existing serializer for the value provider.findValueSerializer(value1.getClass()).serialize(value1, gen, provider);
gen.writeEndObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
);
gen.writeEndObject();
}
}
Running this test :
#JsonTest
class TestSerializerTest {
#Autowired
ObjectMapper objectMapper;
#Test
public void testSerializer() throws Exception {
MyData value = new MyData();
value.setTestValue1("test1");
value.setTestValue2(123);
String s = objectMapper.writeValueAsString(value);
System.out.println(s);
}
}
gives me this output:
{"testValue1":{"isRequired":false,"type":"String","value":"test1"},"testValue2":{"isRequired":false,"type":"Integer","value":123}}
Hope that gives you an idea where to start and how to proceed from here!

CustomDeserializer has no default (no arg) constructor

I am consuming a REST Api with RestTemplate. The response I'm getting from the API has lots of nested objects. Here's a little snippet as an example:
"formularios": [
{
"form_data_id": "123006",
"form_data": {
"form_data_id": "123006",
"form_id": "111",
"efs": {
"1": {},
"2": "{\"t\":\"c\",\"st\":\"m\",\"v\":[{\"id\":\"3675\",\"l\":\"a) Just an example\",\"v\":\"1\"},{\"id\":\"3676\",\"l\":\"b) Another example.\",\"v\":\"2\"}]}"
}
}
The problem I'm having is that most of the times the "1" actually has content, just like "2", and the jackson just parses it as a String on the object "efs". But sometimes, just like in the code snippet, the API sends it empty, and jackson takes it as an Object, which gives me an error that says something about START_OBJECT (can't remember the exact error, but it's not important for this question).
So I decided to make a custom deserializer so when jackson reads "1", it ignores the empty object and just parses it as a null string.
Here's my custom deserializer:
public class CustomDeserializer extends StdDeserializer<Efs> {
public CustomDeserializer(Class<Efs> t) {
super(t);
}
#Override
public Efs deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
String string1 = null;
String string2 = null;
JsonToken currentToken = null;
while ((currentToken = jp.nextValue()) != null) {
if (currentToken.equals(JsonToken.VALUE_STRING)) {
if (jp.getCurrentName().equals("1")) {
string1 = jp.getValueAsString();
} else {
string2 = jp.getValueAsString();
}
} else {
if (jp.getCurrentName().equals("2")) {
string2 = jp.getValueAsString();
}
}
}
return new Efs(string1, string2);
}
}
And this is the way I'm using it when receiving the response from the API:
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("EfsModule");
mod.addDeserializer(Efs.class, new CustomDeserializer(Efs.class));
mapper.registerModule(mod);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
jsonMessageConverter.setObjectMapper(mapper);
messageConverters.add(jsonMessageConverter);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
I'm getting the error:
CustomDeserializer has no default (no arg) constructor
But I don't know exactly what I'm doing wrong nor how to solve it. Thanks for the help and apologies for the long question, I wanted to give as much context as possible.
There is also one trap that users can fall into (like my self). If you declare deserializer as a inner class (not a static nested class) like:
#JsonDeserialize(using = DomainObjectDeserializer.class)
public class DomainObject {
private String key;
public class DomainObjectDeserializer extends StdDeserializer<DomainObject> {
public DomainObjectDeserializer() {
super(DomainObject.class);
}
#Override
public DomainObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// code
}
}
}
Jackson uses the Class#getDeclaredConstructor() with no argument (method accepts vararg) which means: give me a default (no argument) constructor. Code above will throw exception when Jackson tries to create DomainObjectDeserializer because javac generates the constructor that accepts enclosing class reference. Technically speaking DomainObjectDeserializer does not have a default constructor.
For a curiosity sake you can execute DomainObjectDeserializer.class.getDeclaredConstructors() and ensure that method does return single element array containing constructor definition with enclosing class reference.
The DomainObjectDeserializer should be declared as a static class.
Here is a good answer to read in more details.
It is required that you have a default constructor without arguments.
What you can do is create one (or replace the other one if you don't really need it):
public class CustomDeserializer extends StdDeserializer<Efs> {
public CustomDeserializer() {
super(Efs.class);
}
...
}

Custom Jackson Deserializer to omit mapping of certain objects

I have a large JSON file that is made up of structures that I am mapping into POJOs, and then storing in a Collection. The structure is similar to this:
[
{
"id": 1234,
"file": "C:\\Programs\\Program1.exe",
"exists": true
}
{
"id": 5678,
"file": "C:\\Programs\\Program2.exe",
"exists": false
}
...
]
Using the Jackson streaming API I have got all these structures read, and the POJOs stored in a Collection successfully. My POJO class looks like this:
public class Programs
{
#JsonProperty("id")
private Integer id;
#JsonProperty("file")
private String file;
#JsonProperty("exists")
private Boolean exists;
#JsonGetter("id")
public Integer getId()
{
return id;
}
#JsonGetter("file")
public String getFile()
{
return file;
}
#JsonGetter("exists")
public Boolean getExists()
{
return exists;
}
}
However, I want to omit any structures that have "exists" set to false during the deserialization process so that no POJO is ever created for them. So I wrote a custom deserializer with the help of this SO question [ How do I call the default deserializer from a custom deserializer in Jackson ], with my overridden deserialize looking like:
#Override
public Programs deserialize(JsonParser parser, DeserializationContext context)
throws IOException
{
Programs programs = (Programs)defaultDeserializer.deserialize(parser, context);
if (!programs.getExists())
{
throw context.mappingException("[exists] value is false.");
}
return programs;
}
However, when I run some unit tests, I get the following error:
"Can not deserialize instance of java.util.ArrayList out of START_OBJECT token"
message was "Class com.myprogram.serializer.ProgramsJsonDeserializer
has no default (no arg) constructor"
(Adding a no arg constructor gives the error that StdDeserializer does not have a default constructor.)
Is this the correct approach to achieving what I am trying to do? And does anyone know why I get this error message?
I want to omit any structures that have "exists" set to false during
the deserialization process so that no POJO is ever created for them.
I think your objective is to retrieve a list of Programs instance that only have exists set to true after derserialization. A customized CollectionDeserializer to filter those unwanted instance may help:
public class ProgramsCollectionHandler extends SimpleModule {
private static class ProgramsCollectionDeserializer extends CollectionDeserializer {
public ProgramsCollectionDeserializer(CollectionDeserializer deserializer) {
super(deserializer);
}
private static final long serialVersionUID = 1L;
#Override
public Collection<Object> deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
Collection<Object> result = super.deserialize(parser, context);
Collection<Object> filteredResult = new ArrayList<Object>();
for (Object o : result) {
if (o instanceof Programs) {
final Programs programs = (Programs) o;
if (programs.exists) {
filteredResult.add(programs);
}
}
}
return filteredResult;
}
#Override
public CollectionDeserializer createContextual(
DeserializationContext context,
BeanProperty property) throws JsonMappingException {
return new ProgramsCollectionDeserializer(super.createContextual(context, property));
}
}
private static final long serialVersionUID = 1L;
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
context.addBeanDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyCollectionDeserializer(
DeserializationConfig config, CollectionType type,
BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (deserializer instanceof CollectionDeserializer) {
return new ProgramsCollectionDeserializer(
(CollectionDeserializer) deserializer);
}
return super.modifyCollectionDeserializer(config, type,
beanDesc, deserializer);
}
});
}
}
After that, your can register it into your object mapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ProgramsCollectionHandler());
"Can not deserialize instance of java.util.ArrayList out of
START_OBJECT token" message was "Class
com.myprogram.serializer.ProgramsJsonDeserializer has no default (no
arg) constructor"
(Adding a no arg constructor gives the error that StdDeserializer does not have a default constructor.)
This may be because your constructor cannot be accessed. For example, your deserializer is implemented as a non-static inner class.

how to write jackson deserializer based on property in json

I want to write json deserializer on class Type so that when Type is deserialized from given json based on name it maps value (of type interface Being) to its current implementation based on some factory method that returns correct class name based on name, and populates remaining class without any explicit deserialization and without creating object of TigerBeing or HumanBeing explicitly using new.
I tried to use #jsonCreator but there i have to initialize entire HumanBeing or TigerBeing using new and passing all json in constructor. I need auto mapping for types further used as further pojo can be quite complex.
{type:[{
"name": "Human",
"value": {
"height":6,
"weight":100,
"languages":["spanish","english"]
}
},
{
"name":"Tiger",
"value":{
"extinct":1,
"found":["Asia", "America", "Europe", "Africa"]
}
}
]}
I have:
public class Type {
String name;
Being value;
}
public interface Being {
}
public class TigerBeing implements Being {
Integer extinct;
String[] found;
}
public class HumanBeing implement Being {
Integer height;
Integer weight;
String[] languages;
}
import java.io.IOException;
public class BeingDeserializer extends JsonDeserializer<Being> {
#Override
public Expertise deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonMappingException {
JsonNode node = jp.getCodec().readTree(jp);
String beingName = node.get("name").asText();
JsonNode valueNode = node.get("value");
BeingName beingByName = ExpertiseName.getBeingByName(beingName);
if(beingByName ==null) {
throw new JsonMappingException("Invalid Being " + beingName);
}
Being being = JsonUtils.getObjectFromJsonNode(valueNode,
ExpertiseFactory.getExpertise(beingByName).getClass());
return being;
}
}
In this way I was able to solve the above problem.

Encode/decode JSON keys?

I want to send a minified version of my JSON by minifying the keys.
The Input JSON string obtained after marshalling my POJO to JSON:
{
"stateTag" : 1,
"contentSize" : 10,
"content" : {
"type" : "string",
"value" : "Sid"
}
}
Desired JSON STRING which I want to send over the network to minimize payload:
{
"st" : 1,
"cs" : 10,
"ct" : {
"ty" : "string",
"val" : "Sid"
}
}
Is there any standard way in java to achieve this ??
PS: My json string can be nested with other objects which too I will have to minify.
EDIT:
I cannot change my POJOs to provide annotations. I have XSD files from which I generate my java classes. So changing anything there is not an option.
You can achieve this in Jackson by using #JsonProperty annotation.
public class Pojo {
#JsonProperty(value = "st")
private long stateTag;
#JsonProperty(value = "cs")
private long contentSize;
#JsonProperty(value = "ct")
private Content content;
//getters setters
}
public class Content {
#JsonProperty(value = "ty")
private String type;
#JsonProperty(value = "val")
private String value;
}
public class App {
public static void main(String... args) throws JsonProcessingException, IOException {
ObjectMapper om = new ObjectMapper();
Pojo myPojo = new Pojo(1, 10, new Content("string", "sid"));
System.out.print(om.writerWithDefaultPrettyPrinter().writeValueAsString(myPojo));
}
Outputs:
{
"st" : 1,
"cs" : 10,
"ct" : {
"ty" : "string",
"val" : "sid"
}
}
SOLUTION 2 (Using Custom Serializer):
This solution is specific to your pojo, it means for every pojo you will need a new serializer.
public class PojoSerializer extends JsonSerializer<Pojo> {
#Override
public void serialize(Pojo pojo, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
/* your pojo */
jgen.writeStartObject();
jgen.writeNumberField("st", pojo.getStateTag());
jgen.writeNumberField("cs", pojo.getContentSize());
/* inner object */
jgen.writeStartObject();
jgen.writeStringField("ty", pojo.getContent().getType());
jgen.writeStringField("val", pojo.getContent().getValue());
jgen.writeEndObject();
jgen.writeEndObject();
}
#Override
public Class<Pojo> handledType() {
return Pojo.class;
}
}
ObjectMapper om = new ObjectMapper();
Pojo myPojo = new Pojo(1, 10, new Content("string", "sid"));
SimpleModule sm = new SimpleModule();
sm.addSerializer(new PojoSerializer());
System.out.print(om.registerModule(sm).writerWithDefaultPrettyPrinter().writeValueAsString(myPojo));
SOLUTION 3 (Using a naming strategy):
This solution is a general solution.
public class CustomNamingStrategy extends PropertyNamingStrategyBase {
#Override
public String translate(String propertyName) {
// find a naming strategy here
return propertyName;
}
}
ObjectMapper om = new ObjectMapper();
Pojo myPojo = new Pojo(1, 10, new Content("string", "sid"));
om.setPropertyNamingStrategy(new CustomNamingStrategy());
System.out.print(om.writerWithDefaultPrettyPrinter().writeValueAsString(myPojo));
Use the annotations...
with gson:
adding #SerializedName("st") over the Class Member will serialize the variable stateTag as "st" : 1, it doesnt matter how deep in the json you are going to nest the objects.

Categories

Resources