I have problems serializing / deserializing this class with Gson:
public class Test {
#SerializedName("id")
private String mId;
public String getId() { return mId; }
public static Test fromJson(String json) { return new Gson().fromJson(json, Test.class); }
public String toJson() { return new Gson().toJson(this, Test.class); }
}
If I run this:
Test test = Test.fromJson("{\"id\":\"1465988493\"}");
Log.i(TAG, "Test: " + test.toJson());
//Log.i(TAG, "Test id: " + test.getId());
It prints:
Test: {}
But if I run this:
Test test = Test.fromJson("{\"id\":\"1465988493\"}");
Log.i(TAG, "Test: " + test.toJson());
Log.i(TAG, "Test id: " + test.getId());
It works as expected and prints:
Test: {"id":"1465988493"}
Test id: 1465988493
So calling the getter AFTER calling toJson made toJson() to work. WTF???
Last thing, if I initialize the id to null:
public class Test {
#SerializedName("id")
private String mId = null; // <- Init to null
public String getId() { return mId; }
public static Test fromJson(String json) { return new Gson().fromJson(json, Test.class); }
public String toJson() { return new Gson().toJson(this, Test.class); }
}
Then everything works as expected, and this code:
String testJson = "{\"id\":\"1465988493\"}";
Test test = Test.fromJson(testJson);
Log.i(TAG, "Test: " + test.toJson());
//Log.i(TAG, "Test id: " + test.getId());
Prints:
Test: {"id":"1465988493"}
So, I have the solution (initialize all my fields to null), but I'd like to understand what's wrong??
There is just proguard problem. If you use proguard just keep in mind the proguard can remove some fields from class if you don't use it. For example your app doesn't use getters of class. So easy way just add annotation to keep it like:
#SerializedName("id")
#Keep
private String mId;
Or add keep rule to your proguard file.
Related
Given a class Person:
String name;
String surname;
String id;
String address;
I have an object obj1 with the following values:
name="Name"
surname=null
id="ABC123"
address="Here"
Through an api call I get the following json:
{
"name":"John",
"surname":"Doe",
"id":"A1B2C3"
}
which gets mapped into an object obj2like this:
name="John"
surname="Doe"
id="A1B2C3"
address=null
I want to copy all non-null (or empty string) values of obj2 into obj1 so the final result is this:
name="John"
surname="Doe"
id="A1B2C3"
address="Here"
I have two problems.
The first one is that I don't want to have to manually type the get/set call for each attribute of the object.
The second problem, is that I want the method to work for any type of object with no or minimal changes.
At the very least, I need the first problem solved. The second one is optional, but would be great to learn a solution too.
You can use reflection to get all the instance fields and use Field#set to copy over non-null values.
try {
for (Field field : Person.class.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
Object val = field.get(obj2);
if (val != null) {
field.set(obj1, val);
}
}
}
System.out.println(obj1);
} catch (IllegalAccessException e) {
// Handle exception
}
Demo
public static void main(String[] args) {
MyClass obj1 = new MyClass(1,"1");
MyClass obj2 = new MyClass(2,"2");
System.out.println(obj2);
copy(obj1, obj2);
System.out.println(obj2);
}
public static MyClass copy(MyClass obj1, MyClass obj2){
Arrays.stream(obj1.getClass().getDeclaredFields()).forEach(f -> {
try {
f.set(obj2, f.get(obj1));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
return obj2;
}
static class MyClass {
int myInt;
String myString;
String emptyString = null;
public MyClass(int myInt, String myString) {
this.myInt = myInt;
this.myString = myString;
}
#Override
public String toString() {
return "MyClass{" +
"myInt=" + myInt +
", myString='" + myString + '\'' +
", emptyString='" + emptyString + '\'' +
'}';
}
}
in case you have private fields you can use
f.setAccessible(true);
but I don't suggest that.
Reading your question, seems like, you have two classes, in two distinct projects, which, exchange information in json.
So, It is easy if you can use Jackson(default in spring)
You could annotate the target class with the annotation of jackson #JsonInclude(JsonInclude.Include.NON_NULL)
Looking at this code:
#Service
public class WebService {
public WebService() {
this.webClient = WebClient.builder()
.baseUrl(URL)
.build();
}
public Mono<Person> searchById(String id) {
return webClient.get().uri("/v3/"+ id).retrieve().bodyToMono(Person.class);
}
}
And this:
WebService ws = new WebService();
Mono<Person> version = ws.searchById("1");
System.out.println(version.toProcessor().block());
This code works well in getting one person object from a JSON, however I wanted a method to return more than one person from a JSON like so:
public Mono<List<Person>> or public Mono<Person[]>
And I can't seem to make it work. I've tried what I found here, but I don't understand what they are doing here:
return Arrays.stream(objects)
.map(object -> mapper.convertValue(object, Reader.class))
.map(Reader::getFavouriteBook)
.collect(Collectors.toList());
And in my case I want to have a list of objects Person on my main function, not just part of the object like in their case, where they use Reader::getFavouriteBook to apparently map a Book.
Converting this to an answer, for better readability.
WebClient can convert the response body into a mono of array. So, instead of performing bodyToMono(Person.class) we can do bodyToMono(Person[].class).
Now the resulting Mono is a Mono<Person[]>. You can then perform map, subscribe or flatMap as you wish (one or many times) to chain actions. Once all the actions are chained, you can then call block to wait till all the actions are executed and get back the result (or an error).
See this documentation to know more about all the supported methods on Mono.
Note - Using block in a reactive application is not recommended.
EDIT
Here is an example on how a Mono<Person> is converted to a Mono<PersonDTO>
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class Test {
public static void main(String[] args) {
Person person = new Person();
Mono<Person> personMono = Mono.just(person);
Mono<PersonDTO> personDTOMono = personMono.map(p -> {
PersonDTO dto = new PersonDTO();
dto.setName(p.name);
dto.setVersion(p.version);
return dto;
}).delayElement(Duration.of(1000L, ChronoUnit.MILLIS));
System.out.println("Waiting for 1000 millis");
System.out.println("personDTOMono = " + personDTOMono.block());
}
}
class Person {
int version = 10;
String name = "test";
}
class PersonDTO {
private int version;
private String name;
#Override
public String toString() {
return "PersonDTO{" +
"version=" + version +
", name='" + name + '\'' +
'}';
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Using this as a reference I have described the structure of my Json data and can grab the information as needed until I get to nest records and arrays.
Parsing a complex Json Object using GSON in Java
However my JSON data is nested several times over. For example;
{
"meetings": [
{
"meetingName": "GaryVon",
"location": "USA",
"meetingType": "P",
"meetingDate": "2016-03-25",
"weatherCondition": "FINE",
"showCode": {
"meetingCode": "A",
"scheduledType": "R"
},
"venueType": "ANI",
"showPools": [
{
"showProduct": "GaryVon",
"showStatus": "Open",
}
]
}
]
}
I have my wrapper and classes describing the format of the json data. Each class in a new java file.
public class meetingContainer {
public List<meetings> meetings;
}
Top level class
public class meetings {
private String meetingName;
private String location;
private String meetingType;
private String meetingDate;
private String weatherCondition;
private ShowCode showCode;
private String venueType;
private ShowPools[] showPools;
public String getMeetingName() { return meetingName; }
public String getLocation() { return location; }
public String getMeetingType() { return meetingType; }
public String getMeetingDate() { return meetingDate; }
public String getWeatherCondition() { return weatherCondition; }
public ShowCode getShowCode() { return showCode; }
public String getVenueType() { return venueType; }
public ShowPools[] getShowPools() { return showPools; }
}
2nd Level class
public class ShowCode {
private String meetingCode;
private String scheduledType;
public String getMeetingCode() { return meetingCode; }
public String getScheduledType() { return scheduledType; }
}
2nd Level Class
public class ShowPools {
private String showProduct;
private String showStatus;
public String getShowProduct() { return showProduct; }
public String getShowStatus() { return showStatus; }
}
I then try to parse it and grab the data which works fine until I get into nested arrays/records
Gson g = new Gson();
meetingContainer mc = g.fromJson(jsonMeetingsString, meetingContainer.class);
for(meetings m: mc.meetings){
System.out.println(m.getMeetingName()); //Result = "GaryVon"
System.out.println(m.getLocation()); //Result = "USA"
System.out.println(m.getmeetingType()); //Result = "P"
System.out.println(m.getShowCode()); //Result = "packagename.ShowCode#210366b4"
}
My question is how to I declare nested arrays/records and then call those methods from different classes i.e. Call the methods in showcode and showpools. The other post did not say how. Sorry if this is a simple answer as I'm new to java.
m.getShowCode()
This returns a reference of type ShowCode, to access inner values use the getters, for example :
m.getShowCode().getMeetingCode()
You should use a list for showPools
private List<ShowPools> showPools;
Your provided JSON string is invalid. It has one extra , -
{
"showProduct": "GaryVon",
"showStatus": "Open",
^
Answer for your question you asked in comment : m.getShowCode().getShowProduct() is invalid since showCode node has only two attributes meetingCode and scheduledType.
below code is listing all values of JSON. Let me know if it not covers your question
Gson g = new Gson();
meetingContainer mc = g.fromJson(jsonMeetingsString,
meetingContainer.class);
for (meetings m : mc.meetings) {
System.out.println("meetingName: " + m.getMeetingName());
System.out.println("location: "+ m.getLocation());
System.out.println("meetingType: "+ m.getMeetingType());
System.out.println("meetingDate: "+ m.getMeetingDate());
System.out.println("weatherConditio: "+ m.getWeatherCondition());
System.out.println("showCode->meetingCode: "+ m.getShowCode().getMeetingCode());
System.out.println("showCode->scheduledType: "+ m.getShowCode().getScheduledType());
System.out.println("venueType: "+ m.getVenueType());
for(ShowPools showPool : m.getShowPools()){
System.out.println("showPools->showProduct: "+ showPool.getShowProduct());
System.out.println("showPools->showStatus: "+ showPool.getShowStatus());
}
}
Output:
meetingName: GaryVon
location: USA
meetingType: P
meetingDate: 2016-03-25
weatherConditio: FINE
showCode->meetingCode: A
showCode->scheduledType: R
venueType: ANI
showPools->showProduct: GaryVon
showPools->showStatus: Open
I'm working with a JSON file that has nested objects like this,
{
"LocId":99,
"typeId":99,
"name":"foo",
"parentId":99,
"geoCode":
{
"type":"bang",
"coordinates":
[{
"latitude":99.0,
"longitude":99.0
}]
}
}
I created a container to hold the JSON file in a class like this,
public class Location_JSON {
private LocId id;
// +getter+setter
#Override
public String toString() {
return id.toString();
}
public static class LocId {
private Long locId;
private Long typeId;
private String name;
private Long parentId;
private GeoCode geoCode;
// +getters+setters
#Override
public String toString() {
return "{\"locId\":" + locId
+ ", \"typeId\":" + typeId
+ ", \"name\":" + name
+ ", \"geoCode\":" + geoCode.toString() + "}";
}
}
public static class GeoCode {
private String type;
private Coordinates coordinates;
// +getter+setter
#Override
public String toString() {
//return "{\"type\":" + type + "}";
return "{\"type\":" + type
+ ", \"coordinates\":" + coordinates.toString() + "}";
}
}
public static class Coordinates {
private Double latitude;
private Double longitude;
// +getter+setter
#Override
public String toString() {
return "[{\"latitude\":" + latitude
+ ", \"longitude\":" + longitude + "}]";
}
}
}
To test that everything works I read in the JSON object as a string like this,
String str = "the JSON string shown above";
InputStream is = new ByteArrayInputStream(str.getBytes());
BufferedReader br = new BufferedReader(new InputStreamReader(is));
Location_JSON locations = new Gson().fromJson(br, Location_JSON.class);
System.out.println(locations.toString());
This produces a NullPointerException!
I implemented two of the Deserializer solutions found in this SO post,
Get nested JSON object with GSON using retrofit but it still created the same null error.
According to this SO post,
Java - Gson parsing nested within nested what I have should be close.
I tested my code without the nested objects i.e., I erased the nested objects from both the string and the Location_JSON container, and everything worked. So I believe this is a JSON nested object problem.
UPDATE:
If you're looking at this post I just want to point out that I accepted chengpohi's answer because it solved my initial question and chengpohi was the first to provide an answer. I did however have a second problem that I did not discover until after this question was solved. Sachin Gupta provided a working solution to my second problem. If you're using this post please check out BOTH answers down below. Thank you.
Location_JSON locations = new Gson().fromJson(br, Location_JSON.class);
it should be:
LocId locations = new Gson().fromJson(br, LocId.class);
You get NullPointerException, because your LocId have not be initiliazed. Your JSON is a object of LocId.
and your JSON:
"coordinates":
[{
"latitude":99.0,
"longitude":99.0
}]
should be:
"coordinates":
{
"latitude":99.0,
"longitude":99.0
}
As already stated in above answer, you have to use LocId class as primary one.
now for java.lang.IllegalStateException you can modify GeoCode class to use array of Coordinates class. like :
public static class GeoCode {
private String type;
private Coordinates []coordinates;
// +getter+setter
#Override
public String toString() {
return "GeoCode [type=" + type + ", coordinates=" + Arrays.toString(coordinates) + "]";
}
}
I'm trying to #POST a user-created object and get a Response with a different user-created payload as the entity. Although the object returned exists and is populated, on the client end it is empty.
Client sent / server received object:
#XmlRootElement
public class TweetQuery {
String query;
List<TweetQueryTweet> tweets = new ArrayList<>();
// setters and getters
}
public class TweetQueryTweet {
private String id;
private String text;
// setters and getters
}
Server received / client sent object:
#XmlRootElement
public class TweetClusters {
List<TweetCluster> tweetClusters = new ArrayList<>();
// setters and getters
}
public class TweetCluster {
List<String> labels = new ArrayList<>();
List<String> docs = new ArrayList<>();
// setters and getters
}
Client (Arquillian) Test:
#Test
#RunAsClient
public void test01SeeSomething(#ArquillianResource URL deploymentUrl) throws ... {
final URI targetURI = ...;
System.out.println(" test target:" + targetURI.toASCIIString());
Entity<TweetQuery> tweetQuery = Entity.entity(getTestTweetQuery(), MediaType.APPLICATION_JSON);
Client client = ClientBuilder.newBuilder().build();
WebTarget target = client.target(targetURI.toASCIIString());
Response response = target.request(MediaType.APPLICATION_JSON).post(tweetQuery);
TweetClusters x = response.readEntity(TweetClusters.class);
System.out.println("Entity:" + x);
System.out.println("Response: " + response.getStatus());
assertEquals(Status.OK.getStatusCode(), response.getStatus());
assertNotNull(x);
assertThat(x.getTweetClusters().size()).isGreaterThan(0);
}
Jersey Post method:
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response clusterPost(TweetQuery tweetQuery) {
TweetClusters tweetClusters = clusterService.getTweetClusters(tweetQuery);
System.out.println("clusterPost - in - tweetQuery: " + tweetQuery);
System.out.println(" - out tweetClusters: " + tweetClusters);
return Response.status(Status.OK).type(MediaType.APPLICATION_JSON).entity(tweetClusters).build();
}
Debug:
test target:http://localhost:18080/test//cluster
clusterPost - in - tweetQuery: [TweetQuery - query:TweetQuery query, tweets:[[TweetQueryTweet - id:1, text:text 1], [TweetQueryTweet - id:2, text:text 2], [TweetQueryTweet - id:3, text:text 3]]]
- out tweetClusters: [TweetClusters:[[TweetCluster - labels: [Other Topics], docs:[3, 2, 1]]]]
Entity:[TweetClusters:[]]
Response: 200
Line 2 - clusterPost - in -- shows TweetQuery is being marshalled properly.
Line 3 - clusterPost - out -- shows the tweetClusters to be sent as the Response entity exists
Line 4 - tweetClusters is not coming out of the request
Edit
I changed the REST method to return the tweetQuery that it receives as input and it is returned correctly. So its something about TweetClusters. Maybe I need a MessageBodyReader & Writer. Or a Moxy #XmlJavaTypeAdapter. But for what as Lists obviously work out of the box as TweetQuery works.
https://stackoverflow.com/users/383861/blaise-doughan are you out there? :)
Am I missing something simple?
oK I worked it out. I unintentionally told a porkie-pie. When I said I had setters and getters I was mising the setter for TweetClusters. Then once fixed i had a constructor with an argument but no no-arg constructor. Once the no-arg constructor added it was all good.
In summary you need to have in objects to be (un)marshalled:
A no-arg constructor if you have an arg constructor
Setters and getters for all the elements
And if you have more complex types including Date and Calendar you need to have a Adapter #XmlJavaTypeAdapter (Moxy) or #JsonSerialize.using in Jackson (or ??? in RESTeasy ...).
Interestingly I didn't need to have #XmlRootElement (Moxy) though kept it there for good measure.
The complete answer is:
The Client (Arquillian) Test is same as above
The Jersey Post method is same as above
The object classes that (un)marshall are:
#XmlRootElement
public class TweetClusters {
List tweetClusters = new ArrayList<>();
public void addCluster(Cluster c) {
TweetCluster tweetCluster = new TweetCluster(c);
tweetClusters.add(tweetCluster);
}
public List<TweetCluster> getTweetClusters() {
return tweetClusters;
}
public void setTweetClusters(List<TweetCluster> tweetClusters) {
this.tweetClusters = tweetClusters;
}
#Override
public String toString() {
return String.format("[TweetClusters:%s]", tweetClusters);
}
}
public class TweetCluster {
List labels = new ArrayList<>();
List docs = new ArrayList<>();
public TweetCluster() {
}
public TweetCluster(Cluster c) {
labels.add(c.getLabel());
for (Document doc : c.getDocuments()) {
docs.add(doc.getTitle());
}
}
public List<String> getLabels() {
return labels;
}
public void setLabels(List<String> labels) {
this.labels = labels;
}
public List<String> getDocs() {
return docs;
}
public void setDocs(List<String> docs) {
this.docs = docs;
}
#Override
public String toString() {
return String.format("[TweetCluster - labels: %s, docs:%s]", labels, docs);
}
}
public class TweetQuery {
String query;
List<TweetQueryTweet> tweets = new ArrayList<>();
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public List<TweetQueryTweet> getTweets() {
return tweets;
}
public void setTweets(List<TweetQueryTweet> tweets) {
this.tweets = tweets;
}
public void addTweets(TweetQueryTweet... queryTweets) {
for (TweetQueryTweet tweet : queryTweets) {
this.tweets.add(tweet);
}
}
#Override
public String toString() {
return String.format("[TweetQuery - query:%s, tweets:%s]",query, tweets);
}
}
(Arghhh, sorry about the formatting; I can't fix it in SO)
For debugging purposes it is often good to get back the string representation returned from the response (ie. XML or JSON) and you simply specify the entity type as String.class:
String x = response.readEntity(String.class);
Try to return response like this : Response.ok(tweetClusters).build();
I'm not sure why you have the models Annotated with #XmlRootElements. So I would think you could remove that and make sure you are using Jackson to Serialize and Deserialize your request and response body. Which I assume you are or gson.