Spring 4 and RabbitMQ json type - json

I'm really stuck with cast Rabbit response to POJO. How to do it properly? On sprin.io just no practical example.
So I'm trying to do it with this
#Bean
public DefaultClassMapper typeMapper() {
DefaultClassMapper typeMapper = new DefaultClassMapper();
Map<String, Class> idClassMapping = new HashMap<String, Class>();
idClassMapping.put("range", Loan.class);
typeMapper.setIdClassMapping(idClassMapping);
//typeMapper.setDefaultType(Loan.class);
return typeMapper;
}
#Bean
public MessageConverter messageConverter(DefaultClassMapper defaultClassMapper){
JsonMessageConverter jsonMessageConverter = new JsonMessageConverter();
jsonMessageConverter.setClassMapper(defaultClassMapper);
return jsonMessageConverter;
}
"range" actually fake value from example. Also this really doesn't work type problemn on .setIdClassMapping() . Also I can't use default mapper because serve send header without type hinting field. And I have no control on this remote server. Data format always JSON.
Caused by: org.springframework.amqp.support.converter.MessageConversionException: failed to convert Message content. Could not resolve __TypeId__ in header
Any suggestions working example for marshalling/demarshalling Java objects. I have completely different class for send back value from my code. I'm using java 8.

Just write your own ClassMapper - don't use the default one if your decision criteria to choose the class type is not compatible with its internals.
Or, you can subclass the DefaultClassMapper and override getClassIdFieldName() to tell it which message property to use.

Related

How to create a custom POJO for Apache Flink

I'm using Flink to process some JSON-format data coming from some Data Source.
For now, my process is quite simple: extract each element from the JSON-format data and print them into log file.
Here is my piece of code:
// create proper deserializer to deserializer the JSON-format data into ObjectNode
PravegaDeserializationSchema<ObjectNode> adapter = new PravegaDeserializationSchema<>(ObjectNode.class, new JavaSerializer<>());
// create connector to receive data from Pravega
FlinkPravegaReader<ObjectNode> source = FlinkPravegaReader.<ObjectNode>builder()
.withPravegaConfig(pravegaConfig)
.forStream(stream)
.withDeserializationSchema(adapter)
.build();
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<ObjectNode> dataStream = env.addSource(source).name("Pravega Stream");
dataStream.???.print();
Saying that the data coming from Pravega is like this: {"name":"titi", "age":18}
As I said, for now I simply need to extract name and age and print them.
So how could I do this?
As my understanding, I need to make some customized codes at ???. I might need to create a custom POJO class which contains ObjectNode. But I don't know how. I've read the official doc of Flink and also tried to google about how to create a custom POJO for Flink but I can't still figure out clearly.
Could you please show me an example?
Why don't You simply use something more meaningful instead of JavaSerializer? Perhaps something from here.
You could then create a POJO with the fields you want to use and simply deserialize JSON data to Your POJO instead of ObjectNode
Also, if there is some specific reason that You need to have ObjectNode on deserialization then You can simply do something like :
//I assume You have created the class named MyPojo
dataStream.map(new MapFunction<ObjectNode, MyPojo>() {
ObjectMapper mapper = new ObjectMapper();
#Override
public MyPojo map(final ObjectNode value) throws Exception {
mapper.readValue(value.asText(), MyPojo.class)
}
})

Eliminate duplicate Json elements and retrieve element names starting with capital letters spring boot/java

I'm developing a Rest Client using Spring Boot and Spring Framework (spring-boot-starter-parent 2.1.6.RELEASE)
I have a class representing a response object as shown below:
public class ValidateResponse {
private String ResponseCode;
private String ResponseDesc;
//getters and setters
//constructors using fields
//empty constructor
}
I'm creating a web-hook for an external api and I need to return a JSON object to for a specific endpoint (the JSON object properties must start with uppercase(s)). I'm calling returning the object from a PostMapping method nested in a RequestMapping root path:
#PostMapping("hooks/validate")
public ValidateResponse responseObj(#RequestHeader Map<String, String> headersObj) {
ValidateResponse response = new ValidateResponse("000000", "Success");
logger.info("Endpoint = hooks/validate | Request Headers = {}", headersObj);
return response;
}
However, when I hit the endpoint from postman I'm getting duplicate varialbes
{
"ResponseCode": "000000",
"ResponseDesc": "Success",
"responseCode": "000000",
"responseDesc": "Success"
}
I understand that the pojo-json conversion is handled by spring but I don't understand why the conversion is yielding duplicate variables.
Note: I know the ResponseDesc and the ResponseCode are not declared using the best standards for naming variables (camelCasing).
I've done some digging and according to the Java Language Specification
An identifier is an unlimited-length sequence of Java letters and Java digits, the first of which must be a Java letter.
and
The "Java letters" include uppercase and lowercase ASCII Latin letters A-Z (\u0041-\u005a), and a-z (\u0061-\u007a), and, for historical reasons, the ASCII underscore (_, or \u005f) and dollar sign ($, or \u0024). The $ character should be used only in mechanically generated source code or, rarely, to access pre-existing names on legacy systems.
So, I'm assuming its syntactically correct to define a variable using the Camelcase format [Need clarification on this].
I'm considering having to create the JSON object manually but I'd like to know the cause of this behaviour first. Any pointers are appreciated.
Jackson deserializes all the public fields that it comes across. However if you want Jackson to return the response in your expected element names (in your case elements starting with capital letters), make the fields private and annotate them with the #JsonProperty(expected_name_here). Your class file will typically looks as shown below
public class ValidateResponse {
#JsonProperty("ResponseDesc")
private String responseCode;
#JsonProperty("ResponseDesc")
private String responseDesc;
//getters and setters
//constructors using fields
//empty constructor
}
Note: The getters and setters for these fields should be public, otherwise Jackson won't see anything to deserialize in the class.
public class ValidateResponse {
#JsonProperty("ResponseDesc")
public String responseCode;
#JsonProperty("ResponseDesc")
public String responseDesc;
//getters and setters
//constructors using fields
//empty constructor
}
This must fix your problem, however I do not know the reason as it requires deep Jackson investigation.
EDIT
I found out the reason.
The field got duplicated because in you case you had:
2 public fields named in upper case -> they are to be processed by jackson
2 getters getResponseCode and getResponseDesc -> they are to be resolved
as accessors for properties responseCode and responseDesc accordingly.
Summing this up - you have 4 properties resolved by Jackson. Simply making your fields private will resolve your issue, however I still advise using JsonProperty approach.
I added a com.google.code.gson dependency in the projects pom.xml file to configure Spring Boot to use Gson (instead of the default jackson).
The Json object returned from the hooks/validate endpoint must have its property names starting with a capital letter. Using a java class to generate the response object was resulting to camelCased property names so I resolved to create the JSON response object manually. Here's the code for creating the custom JSON object:
public ResponseEntity<String> responseObj(#RequestHeader Map<String, String> headersObj) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
JsonObject response = new JsonObject();
response.addProperty("ResponseCode", "00000000");
response.addProperty("ResponseDesc" , "Success");
logger.info("Endpoint = hooks/validate | Request Headers = {}", headersObj);
return ResponseEntity.ok().headers(responseHeaders).body(response.toString());
}
Note The JSON object is returned as a String so the response from the endpoint must have an additional header to define MediaType to inform the calling system that the response is in JSON format:
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
then add the header to the response:
return ResponseEntity.ok().headers(responseHeaders).body(response.toString());

Jersey/Genson: Unmarschalling single object array

Similar to Jersey: Json array with 1 element is serialized as object BUT on the client side. E.g. I recieve a JSON object where a field is an array regulary, but in case there is only one element, it is a single object.
{"fileInfo":[{"fileName":"weather.arff","id":"10"},"fileName":"supermarket.arff","id":"11"}]}
versus
{"fileInfo":{"fileName":"weather.arff","id":"10"}}
I'm parsing/unmarshalling the JSON using Jersey/Genson. Of course, if the JSON doesnt match the target class I recieve an error (such as expected [ but read '{' )
I've read a lot about this bug and how to avoid when creating JSON objects on the SERVER side, but I found nothing about how to handle this issus when dealing on the CLIENT side.
As always, I prefere the most codeless possibility if there are several solutions...
BTW: Moxy works but it does not marshal native Object-type objects which is another requirement...
Update
Starting with Genson 1.3 release you can achieve it by enabling permissiveParsing:
Genson genson = new GensonBuilder().usePermissiveParsing(true).create();
Answer
Uh, do you know what library produces this on server side? I am curious to see who is responsible for all those badly structured jsons out there...
It is not yet supported in Genson. Originally because IMO people should not produce such dynamic json. Anyway, I opened an issue - this can be easily done, you can expect it to be present in the release coming next week.
Otherwise here is a way to achieve it without breaking the existing mechanisms.
You need to register a Factory that will use Gensons collections factory to create an instance of its standard collection converter. Then you will wrap this converter in another one that will handle the object to array logic. Here is the code (not codeless..., but if you wait a bit you won't have to code :)).
import com.owlike.genson.convert.DefaultConverters.CollectionConverterFactory;
import com.owlike.genson.convert.DefaultConverters.CollectionConverterFactory;
class SingleObjectAsCollectionFactory implements Factory<Converter<Collection>> {
// get the default factory
Factory<Converter<Collection<?>>> defaultFactory = CollectionConverterFactory.instance;
#Override
public Converter<Collection> create(Type type, Genson genson) {
// obtain an instance of the correct default converter for this type
final CollectionConverter defaultConverter = (CollectionConverter) defaultFactory.create(type, genson);
// wrap it in your own converter
return new Converter<Collection>() {
#Override
public void serialize(Collection object, ObjectWriter writer, Context ctx) throws Exception {
defaultConverter.serialize(object, writer, ctx);
}
#Override
public Collection deserialize(ObjectReader reader, Context ctx) throws Exception {
if (reader.getValueType() == ValueType.OBJECT) {
Object object = defaultConverter.getElementConverter().deserialize(reader, ctx);
Collection result = defaultConverter.create();
result.add(object);
return result;
} else return defaultConverter.deserialize( reader, ctx );
}
};
}
}
And then register it
Genson genson = new GensonBuilder()
.withConverterFactory(new SingleObjectAsCollectionFactory())
.create();

JAX-RS / Jersey ".get(Integer.class)" and single JSON primitive (Integer) values?

I have a JAX-RS WebService with the following method:
#Path("/myrest")
public class MyRestResource {
...
#GET
#Path("/getInteger")
#Produces(APPLICATION_JSON)
public Integer getInteger() {
return 42;
}
When accessed using this snipped:
#Test
public void testGetPrimitiveWrapers() throws IOException {
// this works:
assertEquals(new Integer(42), new ObjectMapper().readValue("42", Integer.class));
// that fails:
assertEquals(new Integer(42), resource().path("/myrest/getInteger").get(Integer.class));
}
I get the following exception:
com.sun.jersey.api.client.ClientResponse getEntity
SEVERE: A message body reader for Java class java.lang.Integer, and Java type class java.lang.Integer, and MIME media type application/json was not found
com.sun.jersey.api.client.ClientResponse getEntity
SEVERE: The registered message body readers compatible with the MIME media type are: application/json
...
The problem is just with returning single primitive values (int/boolean) or their wrapper classes. Returning other POJO classes is not the problemen so I guess all the answers regarding JSONConfiguration.FEATURE_POJO_MAPPING and JAXB annotations do not apply here.
Or which annotation should I use to describe the return type if I don't have access to its
class source?
Using ngrep I can verify that just the String "42" is returned by the webservice. Thats a valid JSON "value" but not a valid JSON "text" according to the spec. So is my problem on the client or the server side?
I tried activating JSONConfiguration natural/badgerfish according to http://tugdualgrall.blogspot.de/2011/09/jax-rs-jersey-and-single-element-arrays.html but with no success (ngrep still shows just "42"). Would that be the right path?
Any ideas are appreciated!
This is a recognized bug in Jackson, which has been touted (incorrectly in my opinion) as a feature. Why do I consider it a bug? Because while serialization works, deserialization definitely does not.
In any case, valid JSON cannot be generated from your current return type, so I would recommend creating a wrapper class:
class Result<T> {
private T data;
// constructors, getters, setters
}
#GET
#Path("/getInteger")
#Produces(APPLICATION_JSON)
public Result<Integer> getInteger() {
return new Result<Integer)(42);
}
Alternatively, you can elect to wrap root values, which will automatically encapsulate your data in a top level JSON object, keyed by the objects simple type name - but note that if this option is used that all generated JSON will be wrapped (not just for primitives):
final ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
final String serializedJson = mapper.writeValueAsString(42);
final Integer deserializedVal = mapper.readValue(serializedJson,
Integer.class);
System.out.println(serializedJson);
System.out.println("Deserialized Value: " + deserializedVal);
Output:
{"Integer":42}
Deserialized Value: 42
See this answer for details on how to retrieve and configure your ObjectMapper instance in a JAX-RS environment.

Dynamically ignore properties with JacksonJson

I'm aware that there are multiple way to tell JacksonJson to ignore
properties during rendering but all of them are static. (JasonIgnore, MixIn classes, ..).
This is my scenario. A domain object can implement a interface called FilteredDomain to
allow it to be dynamically filtered. The interface is simple and only exposes one method
"getIgnoreProperties". (A list of properties to ignore).
I then register a Custom Serializer that binds to the FilteredDomain object. The
code looks something like:
private class FilteredDomainSerializer extends JsonSerializer<FilteredDomain> {
public void serialize(FilteredDomain arg, JsonGenerator jgen,
SerializerProvider provder) throws IOException,
JsonProcessingException {
final BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(arg);
for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
final String name = pd.getName();
if (arg.getIgnoreProperties().containsKey(name))
continue;
final Object value = wrapper.getPropertyValue(name);
jgen.writeObjectField(name, value);
}
}
}
First, I really dislike that I need to use the Spring Bean wrapper to get a list of all properties and iterate through them (There must be a way to do this is jackson json).
Second, The code still dosen't work. I get the error:
org.codehaus.jackson.JsonGenerationException: Can not write a field name, expecting a value
at org.codehaus.jackson.impl.JsonGeneratorBase._reportError(JsonGeneratorBase.java:480)
at org.codehaus.jackson.impl.Utf8Generator.writeFieldName(Utf8Generator.java:270)
at org.codehaus.jackson.JsonGenerator.writeObjectField(JsonGenerator.java:1088)
at com.rootmusic.util.SystemJsonObjectMapper$ValueObjectSerializer.serialize(SystemJsonObjectMapper.java:65)
at com.rootmusic.util.SystemJsonObjectMapper$ValueObjectSerializer.serialize(SystemJsonObjectMapper.java:1)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:304)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:254)
at org.codehaus.jackson.map.ser.ContainerSerializers$AsArraySerializer.serialize(ContainerSerializers.java:142)
at org.codehaus.jackson.map.ser.MapSerializer.serializeFields(MapSerializer.java:287)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:212)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:23)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:606)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:280)
The error comes from the fact that you are not writing START_OBJECT / END_OBJECT around field-name/value pairs, so that should be easy to fix.
As to more dynamic filtering, you could read this blog entry which includes standard methods. #JsonView works if you have sets of static definitions (one of which you can dynamically select on per-serialization basis), but if you want yet more dynamic system, #JsonFilter is the way to go.
Alternatively, another relatively simple way would be to first "convert" your POJO into a Map:
Map props = objectMapper.convertValue(pojo, Map.class);
(which is similar to serializing it as JSON, except that result is a Map which would render as JSON)
and then selectively trim Map, and serialize that as JSON. Or, if you prefer, you can use JsonNode ("tree model") as the intermediate thing to modify and then serialize.