I'm trying to send a POJO over an RSocket requestStream:
import java.io.Serializable;
class GreetingRequest implements Serializable {
private String name;
public GreetingRequest() {}
public GreetingRequest(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
If I were to send a String I can do:
ByteBuf data = ByteBufAllocator.DEFAULT.buffer().writeBytes("Hello".getBytes());
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
But how can I serialise my POJO?
This is my attempt using implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' which doesn't work:
GreetingRequest pojo = new GreetingRequest("Davide");
ByteBuf data = SerializationUtils.serialize(pojo);
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
There is a native java serialization mechanism that I would NOT recommend, but you can read about it. Read about Serialazable interface in Java API. There are 2 options that I would recommend:
JSON-JACKSON (also known as Faster XML)
GSON (mentioned in the answer from César Ferreira)
Both convert classes to JSON and vise-versa. For JSON-JACKSON see class ObectMapper. In particular methods writeValueAsString() or writeValueAsBytes() to serialize your object to JSON string or bytes. And to convert it back look for method readValue().
Here are the Maven artifacts that you would need to use it:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.3</version>
</dependency>
I recommend you using Gson converter. It helps you to convert a Java Class to a JSON String. And then you can work with the String as if you were working with simple text.
You can import the dependency:
dependencies {
implementation 'com.google.code.gson:gson:2.8.7'
}
And then, can use jsonschema2pojo to convert the JSON:
{ "name": "Test" }
to classes like this:
package com.example;
import javax.annotation.Generated;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class GreetingRequest {
#SerializedName("name")
#Expose
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
After all is done you can do something like this in Java:
Gson converter = new Gson();
GreetingsRequest request = new GreetingRequest();
request.setName("Test");
String greetingsJSON = converter.toJson(request);
And then you can still send the JSON string as it follows:
ByteBuf data = ByteBufAllocator.DEFAULT.buffer().writeBytes(greetingsJSON.getBytes());
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
Data conversions:
JSON Object - Java class
Array - List<>
Helpful links:
This is the library you need to include (tutorials included) in Java: GSON Converter Git
This is an JSON to Class online converter: Jsonschema2pojo generator
If you use a framework like Spring Boot this is taken care of for you. You may want to manually control in which case the other examples are more relevant, but there are productivity benefits to Spring Boot or rsocket-rpc.
https://github.com/rsocket/rsocket-demo/blob/master/src/main/kotlin/io/rsocket/demo/chat/ChatController.kt
https://spring.io/blog/2020/03/02/getting-started-with-rsocket-spring-boot-server
or rsocket-rpc-java using protobuf instead of Serialization
https://github.com/rsocket/rsocket-rpc-java/blob/master/docs/get-started.md
Related
I have the following YAML I want to parse using Jackson parser in Java.
android:
"7.0":
- nexus
- S8
"6.0":
- s7
- g5
ios:
"10.0":
- iphone 7
- iphone 8
I created a created class which has getter and setter as Java Object for android. It works fine. But how do I do the same for 6.0 and 7.0? I'm usingJackson` Parser
No idea whether Jackson supports that; here's a solution with plain SnakeYaml (I will never understand why people use Jackson for parsing YAML when all it does is basically take away the detailed configuration possible with SnakeYaml which it uses as backend):
class AndroidValues {
// showing what needs to be done for "7.0". "8.0" works similarly.
private List<String> v7_0;
public List<String> getValuesFor7_0() {
return v7_0;
}
public void setValuesFor7_0(List<String> value) {
v7_0 = value;
}
}
// ... in your loading code:
Constructor constructor = new Constructor(YourRoot.class);
TypeDescription androidDesc = new TypeDescription(AndroidValues.class);
androidDesc.substituteProperty("7.0", List.class, "getValuesFor7_0", "setValuesFor7_0");
androidDesc.putListPropertyType("7.0", String.class);
constructor.addTypeDescription(androidDesc);
Yaml yaml = new Yaml(constructor);
// and then load the root type with it
Note: Code has not been tested.
I think that you should try annotation com.fasterxml.jackson.annotation.JsonProperty. I'll provide a short example below.
Sample YAML file:
---
"42": "some value"
Data transfer object class:
public class Entity {
#JsonProperty("42")
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Parser:
public class Parser {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
Entity entity = mapper.readValue(new File("src/main/resources/sample.yml"), Entity.class);
System.out.println(entity.getValue());
}
}
The console output should be: some value.
P.S. I tested it with the following dependencies:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>
I'm calling an aws lambda with a json body. So the fields of the json are with different name from the ones in the POJO. So what I did is to add #JsonProperty on the fields to tell jackson what are the names in json. But for some reason it seems that it doesn't recognize them and all the fields are null. If I pass a json with the same field names as the POJO it's working. Here's my class:
public class Event implements Identifiable {
#JsonProperty("distinct_id")
private String distinctId;
#JsonProperty("user_id")
private Integer userId;
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime eventDateTime;
//Here are the getters and setters
}
If I pass
{"distinct_id":"123", "user_id":123, "dt":"2017-01-04T08:45:04+00:00"}
all the fields are null and with distinctId, userId, eventDateTime it's serializing ok with the exception that it also doesn't recognize my custom serializers/deserializers but this actually is the same problem.
My conclusion is that for some reason the aws jackson is not working with the annotations but it doesn't make sense.
So I found a way to do this. You need to implement RequestStreamHandler which gives you input and output streams which you can work with:
import com.amazonaws.services.lambda.runtime.RequestStreamHandler
public class ChartHandler implements RequestStreamHandler {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
DeserializationClass deserializedInput = objectMapper.readValue(inputStream, DeserializationClass.class)
objectMapper.writeValue(outputStream, deserializedInput); //write to the outputStream what you want to return
}
}
Having the input and output streams makes you independent of the format and frameworks you use to parse it.
Take a look at this quote from AWS documentation:
You shouldn't rely on any other features of serialization frameworks such as annotations. If you need to customize the serialization behavior, you can use the raw byte stream to use your own serialization.
From: https://docs.aws.amazon.com/lambda/latest/dg/java-programming-model-req-resp.html
It sounds like you have version mismatch between annotation types, and databind (ObjectMapper): both MUST be the same major version. Specifically, Jackson 1.x annotations work with Jackson 1.x databind; and 2.x with 2.x.
Difference is visible via Java package: Jackson 1.x uses org.codehaus.jackson, whereas Jackson 2.x uses com.fasterxml.jackson. Make sure to import right annotations for ObjectMapper you use.
I had this same issue and needed MyCustomClass to be taken in and out of the Lambda Function correctly so that it can be passed through my State Machine in the Step Function without any hiccups.
Building off what Hristo Angelov posted, I was able to get a solution that worked for me and I'm posting it hoping that it will help others that were stuck like I was:
import java.io.InputStream;
import java.io.OutputStream;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
public class StaticSearchPagingLambdaFunctionHandler implements RequestStreamHandler {
LambdaLogger logger = null;
MyCustomClass myCustomClass = null;
// Register the JavaTimeModule for LocalDate conversion
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) {
myCustomClass = objectMapper.readValue(inputStream, MyCustomClass .class);
// ...
// Do stuff with myCustomClass
// ...
objectMapper.writeValue(outputStream, myCustomClass);
}
}
Even though the JSON string will print out differently with the ObjectMapper writing to the OutPutStream, when the next lambda function takes it in while going through the Step Function, it will still get converted to LocalDate correctly.
Make sure that in MyCustomClass your toString() method prints correctly. My toString() method looks like this:
import java.time.LocalDate;
import org.json.JSONObject;
public class SimpleSearch {
private LocalDate startDate;
private LocalDate endDate;
// ...
// Getters and Setters for the LocalDate variables
// ...
#Override
public String toString() {
return new JSONObject(this).toString();
}
public SimpleSearch() {}
}
then your JSON printouts will always look like this when it gets sent to the lambda and not that other crazy Jackson format:
{
"startDate": "2018-11-01",
"endDate": "2018-11-16"
}
Some of the Maven dependencies I used:
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.7</version>
</dependency>
Hopefully AWS fixes the Jackson conversions to be reciprocal, to and from JSON, so that we wouldn't have to resort to these custom conversions anymore.
create getter methods for the properties and put #JsonProperty on the getter methods.
I'm implementing RESTFul web service using Jersey 2.22.1 with MOXY as Json Provider.
For example I have the following entity User:
public class User {
private String id;
private String email;
private Address address;
private List<Phone> phones;
// getters & setters
}
and additional classes
public class Address {
private String type;
private String value;
// getters & setters
}
public class Phone {
private String type;
private String value;
// getters & setters
}
This is my JAX-RS resource implementation:
#POST
public Response create(User user) {
// some logic
}
Now when I'm sending POST request containting following json data:
{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
it works perfectly, MOXY automatically maps this json to user object and it's fine
But I need to handle json with another level of nesting, like this:
{
"user":{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
}
As you can see there is another key called user, and I know it's not a good json structure but it's a requirement and I have to accept it as it is. Now I need to be able to handle it. For now I can see only one solution.
I can add another one class wrapper aroung User and pass it to the create method.
So it would look this:
JAX-RS resource:
#POST
public Response create(UserWrapper user) {
// some logic
}
And java class:
public class UserWrapper {
private User user;
// getters & setters
}
It's working solution but I don't really like it because I need to add one more additional class. Would like to here your suggestions how to keep my java classes as it is and be able to accept json with one more level of nesting (i mean this user key).
Thanks in advance!
May not be the answer you're looking for, but I recommend using Jackson instead of MOXy. It's a more mature JSON framework with more features, and just works better. There may be a way with MOXy, but here is the Jackson way
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.1</version>
</dependency>
<!-- You need to remember to remove MOXy -->
In a ContextResolver, configure the ObjectMapper to unwrap the root value
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper mapper;
public MyObjectMapperProvider() {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
}
private static ObjectMapper createDefaultMapper() {
return mapper;
}
}
The value it will look for to deserialize will either be
The value in a #JsonRootName annotation, e.g. #JsonRootName("user") (on the class)
The value in a #XmlRootElement annotation, e.g. #XmlRootElment(name="user") (on the class)
If there is no annotation, then the name of the class, with the first letter lower cased.
Also not, unless you are using any MOXy specific features, making the switch to Jackson, you probably will not need to make any changes at all to your classes. Jackson also supports JAXB annotations (for the most part).
If you want the response to be wrapped, you can also use
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
I am working on a simple example using Jackson library to convert a json string back to Java object but I see only few properties are being set on my java object instead of all properties.
Here is my code:
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import org.codehaus.jackson.map.ObjectMapper;
public class JsonTest {
public static void main(String[] args) throws FileNotFoundException, IOException {
StringBuffer buffer = new StringBuffer();
String data = "";
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("path-to-sample.json"));
while ((data = reader.readLine()) != null) {
buffer.append(data);
}
} finally {
if (reader != null) {
reader.close();
}
}
System.out.println(buffer.toString());
ObjectMapper mapper = new ObjectMapper();
Sample obj = mapper.readValue(buffer.toString(), Sample.class);
System.out.println(obj);
}
}
The Sample.java program looks like this:
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Sample {
#JsonProperty("prop_1")
private String prop1;
private String prop2;
#JsonProperty("prop_3")
private String prop3;
private String prop4;
// Setters & Getters for the properties.
#Override
public String toString() {
return "Sample [prop1=" + prop1 + ", prop2=" + prop2 + ", prop3="
+ prop3 + ", prop4=" + prop4 + "]";
}
}
Input json string in my file is :
{
"prop_1": "1",
"prop2": "2",
"prop_3": "3",
"prop4": "4"
}
The output of this program is :
Sample [prop1=null, prop2=2, prop3=null, prop4=4]
As per my program the prop1 and prop3 should not be null. I am not clear where I made mistake.
Update:
If I remove the #JsonProperty annotation then I am getting the exception as :
Exception in thread "main" org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "prop_1" (Class Sample), not marked as ignorable
This is my pom.xml file dependencies:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
You said in your comment, that you're using Jackson in version "2.5.4", but you're importing the ObjectMapper class from the org.codehaus package. This means, that this class is from version 1.9.13 (or from an older version).
I can reproduce your problem if I mix the versions using ObjectMapper and JsonIgnoreProperties from version 1.9.13 (org.codehaus) and JsonProperty from version 2.6.0 (com.fasterxml).
Output:
Sample [prop1=null, prop2=2, prop3=null, prop4=4]
If I only use version 1.9.13 or 2.6.0, then the result is ok:
Sample [prop1=1, prop2=2, prop3=3, prop4=4]
(for both)
So I recommend to make sure that you don't mix the used libraries and I recommend to use the newest version, which is from FasterXML.
But the used version is up to you.
You can download the jar file from here:
org.codehaus.jackson v1.9.13
com.fasterxml.jackson (core) v2.6.0
Btw about your comment:
#OldCurmudgeon, Thanks for responding. Changing the fields to public has not fixed the issue. I have removed the #JsonProperty annotation and then changed the setter methods to setProp_1 & setProp_3, it worked. So does it mean that there is an issue with #JsonProperty annotation?
Yes, you have (or hopefully had :P) a problem with that annotation: it was from a different Jackson version.
About your edit:
The link to the Jackson lib from fasterXML in the maven repository has one big advantage: it shows you which lib you should download to work with Jackson in your project.
You need:
Jackson Databind (which also has the ObjectMapper class)
Jackson Core
Jackson Annotations
Is it a nice practice to retrieve a JSON object as a String and then parse it manually inside the application or there is a better way to get a transfer object representation (eg. some tools or comfortable APIs, automated mapping services, don't know)?
Example:
#POST
#Path("/myUrlPath")
public Response postSomething(String jsonAsString) {
JSON json = getFromMyCustomParser(jsonAsString);
MyObject myObject = getFromMyCustomMapper(json);
//business logic
}
Don't know much about this topic.
You can actually accept a JSON as a parameter of your resource method. The Jersey REST API would support this. You might have to add a JSON library as a dependency.
I think that this is the JSON dependency we use:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.12</version>
</dependency>
In this case we use the JSONObject class from org.codehaus.jettison.json.
There's a tutorial on how to do this using the Jersey framework. It explains POJO mapping which is based on Jackson. You may have to configure POJO mapping yourself.
Heres an example for Jackson:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(new Something("Name")));
public class Something {
private String name;
public Something(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Output: {"name":"Name"}
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.3.1</version>
</dependency>
Further reading: Jackson
Example for org.json.JSONObject:
JSONObject json = new JSONObject();
List<Object> list = new ArrayList<>();
list.add("Hello");
list.add("Hello2");
list.add("Hello3");
json.put("List", list);
System.out.println(json.toString());
Output: {"List":["Hello","Hello2","Hello3"]}
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
Further reading: JSON.org
Example for GSON
(https://sites.google.com/site/gson/gson-user-guide#TOC-Object-Examples)
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
Deserialization
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
Output: {"value1":1,"value2":"abc"}
Further reading: GSON