In my MongoDB a document looks like this:
{
"_id" : ObjectId("5613700bc00eac21886b6a51"),
"firstname" : "Marc",
"lastname" : "Anonymous",
"email" : "marc.XXXXXX#hotmail.com",
"phone" : "+41/12/345678",
"timestamp" : ISODate("2015-10-06T06:54:03.905+0000"),
"state" : "waiting"
}
I am using gson to parse the json into my Java class User which has a variable timestamp and is a Date but I get the following error:
Caused by: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 179 path $.timestamp
I insert the timestamp into MongoDb via Date too. I don't know how to handle this. Should I just use a String and convert it every time I need in in Date form?
public class User {
public String firstname;
public String lastname;
public String email;
public String phone;
public Date timestamp;
public String state;
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
//...
Edit
I get the json via the MongoDb Java driver. The error occurs when I want to deserialize the String with gson
public ArrayList<User> findUserByEmail(String email) {
Gson g = new Gson();
ArrayList<User> l = new ArrayList<>();
MongoDatabase db = con.getDatabase("waitinglist");
MongoCollection col = db.getCollection("users");
MongoCursor<Document> f = col.find(eq("email", email)).iterator();
while (f.hasNext()) {
Document d = f.next();
System.out.println(d.toJson());
l.add(g.fromJson(d.toJson(), User.class));
}
return l;
}
d.toJson() returns
{
"_id": {
"$oid": "5613700bc00eac21886b6a51"
},
"firstname": "Marc",
"lastname": "xxxx",
"email": "marc.xxxxxx#hotmail.com",
"phone": "+41/12/345678",
"timestamp": {
"$date": 1444114443905
},
"state": "waiting"
}
The error is being thrown because the Date gets convertetd into an own document and when I want to deseriazable it with Gson the Json does not fit with my class.
You can solve this problem by using Jongo and changing your code to the following:
public ArrayList<User> findUserByEmail(String email) {
Gson g = new Gson();
ArrayList<User> l = new ArrayList<>();
Jongo jongo = con.getDB("waitinglist");
Iterator<User> users = jongo.getCollection("users").find("{email: #}", email).as(User.class).iterator();
while(users.hasNext()){
l.add(users.next());
}
return l;
}
Otherwise you're going to have to do something like the following...
Make a wrapper class like this
public static class GsonCompatibleDate {
#SerializedName("$date")
public Long date;
public GsonCompatibleDate(Long date) {
this.date = date;
}
public Date getDate() {
return new Date(date);
}
public void setDate(Date date) {
this.date = date.getTime();
}
}
Note the annotation #SerializedName("$date"). This just lets you use a nicer Java-style name for your variable;
Update your POJO to this or something similar:
public class User {
public String firstname;
public String lastname;
public String email;
public String phone;
public GsonCompatibleDate timestamp;
public String state;
public Date getTimestamp() {
return timestamp.getDate();
}
}
Your de-serialization code would stay exactly as it was before. The updated POJO now matches your Mongo JSON output.
I tested both methods and they work for me. I also highly recommend using a library like Jongo, Morphia, etc... instead of using the Mongo driver itself, unless absolutely necessary.
Related
I have a java spring boot rest controller which accepts a java object.
#PutMapping(path = "api/v1/examples/{id}/update")
public ResponseEntity<Void> updateExample(#PathVariable("id") String personsId, #RequestBody ExampleDto dto) {
return ResponseEntity.noContent().build();
}
The ExampleDto looks like this
public class Example {
#ApiModelProperty(example = "2021-02-18T13:45:07+02:00", notes = "Date pattern is yyyy-MM-dd'T'HH:mm:ssZ example value 2021-02-18T13:45:07+02:00")
private ZonedDateTime date;
public String getDate() {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"));
}
public void setDate(String date) {
this.date = ZonedDateTime.parse(date, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
This works the way I like it. It generates the swagger file correctly and in the swaggerui It suggests the correct value which my dto can parse with the setDate(String date) method.
But I would like to make a reusable component that I can just add to my api dtos where I wont have to create all the methots.
If I wanted to add multible date fields my code would look like this.
public class Example {
#ApiModelProperty(example = "2021-02-18T13:45:07+02:00", notes = "Date pattern is yyyy-MM-dd'T'HH:mm:ssZ example value 2021-02-18T13:45:07+02:00")
private ZonedDateTime date;
public String getDate() {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"));
}
public void setDate(String date) {
this.date = ZonedDateTime.parse(date, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
#ApiModelProperty(example = "2021-02-18T13:45:07+02:00", notes = "Date pattern is yyyy-MM-dd'T'HH:mm:ssZ example value 2021-02-18T13:45:07+02:00")
private ZonedDateTime dateTwo;
public String getDateTwo() {
return dateTwo.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"));
}
public void setDateTwo(String date) {
this.dateTwo = ZonedDateTime.parse(date, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
I would like if I could do something like this.
#Getter
#Setter
public class Example {
private MyDateType date;
private MyDateType dateTwo;
}
I can already do this. But then my json would look like this.
{
"date": {
"innerDate": "2021-02-18T13:45:07+02:00"
},
"dateTwo": {
"innerDate": "2021-02-18T13:45:07+02:00"
},
}
And I want the json to look like this
{
"date": "2021-02-18T13:45:07+02:00",
"dateTwo": "2021-02-18T13:45:07+02:00"
}
I have a Java Class named User with
#Column(name = "id")
private Long id;
#NotNull
#Column(name = "NAME")
private String name;
I am trying to get some details in a list and convert it into JSOn like so:
Session session = this.sessionFactory.getCurrentSession();
String queryString="select id,name from User where unit=:name";
Query query= sessionFactory.getCurrentSession().createQuery(queryString);
query.setParameter("name", name);
List<User> users= (List<User>) query.list();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Map<String, List<User>> wrap = new HashMap<>();
wrap.put("users", users); // wrap user list in a map
String json = gson.toJson(wrap);
This produces a JSON
{
"users": [
[
1,
"Room"
],
[
2,
"Regi"
],
]
}
How do I change it so that I get a JSON like
{
"users": [
[
"id":1,
"name":"Rovom"
],
[
"id":2,
"name":"Regi"
],
]
}
Edit
I realized it is the query that is causing the issue. If i use
String queryString="from User where unit=:name";
It gives the correct format. How do I fix this?
Whith jackson, it'll look like this:
String json = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueToString(wrap);
You'd want jackson-core and jackson-databind for this at least.
Full example using Jackson:
public static class User {
private Long id;
private String name;
public User(long i, String n) {
id = i;
name = n;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String... args) {
try {
Map<String,Object> map = new HashMap<>();
map.put("users", Arrays.asList(new User(1, "Stack"), new User(2, "Overflow")));
System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter()
.writeValueAsString(map));
} catch (Exception e) {
e.printStackTrace();
}
}
produced this output:
{
"users" : [ {
"id" : 1,
"name" : "Stack"
}, {
"id" : 2,
"name" : "Overflow"
} ]
}
Hm, looks like type erasure at runtime.
Your List<User> is with the first query actually a List<Object[]> as which it got serialized. Would you try to access an User-object out of the list, you'd get a runtime error, I suppose.
See hibernate documentation:
Return the query results as a List. If the query contains multiple
results pre row, the results are returned in an instance of Object[].
EDIT
to get a list of Users with only the two fields filled, create the apropriate constructur and use a query like
select new package.path.to.class.User(id,name) from User where unit=:name"
As mentioned by user #Turo this is because of type erasure at runtime.
To fix this, the query has to be changed to
String queryString="select id,name from User where unit=:name";
Query query= sessionFactory.getCurrentSession().createSQLQuery(queryString).addScalar("name", new StringType()).addScalar("id", new IntType()).setResultTransformer(Transformers.aliasToBean(User.class));
query.setParameter("name", name);
the addScalar() will map the values to the User objects and this gives the required result.
I've got an JSON string from my API, looks like this:
[
{
"id": "abc",
"data": {
"Name": "Peter",
"Date": "2017/12/01"
}
},
{
"id": "def",
"data": {
"Name": "Tina",
"Date": "2017/12/20"
}
},
{
"id": "ghi",
"data": {
"Name": "Amy",
"Date": "2017/12/16"
}
}
]
Then, I use (java):
Gson gson = new Gson();
Type resultType = new TypeToken<List<Map<String, Object>>>() {
}.getType();
List<Map<String, Object>> result = gson.fromJson(info, resultType);
if I call result.get(0).toString());
then it returned:
{id=abc, data={Name=Peter, Date=2017/12/01}}
if I call result.get(0).get("id").toString();
then it returned
abc
Now I want to get the data of "data", when I call result.get(0).get("data").toString();
then it returned
{Name=Peter, Date=2017/12/01}
Finally I want to get the "Name" info, but when I tried to convert this string to Map, it cause some problem, the code is like this:
Type type = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> myMap = gson.fromJson(str, type);
This doesn't work. I found that maybe the string is not a general type of JSON, it is like "Name=Peter, Date=2017/12/01", but it needs "Name": "Peter", "Date": "2017/12/01" , right? Is that the problem? How can I get the data of Name? Can anyone help me?
Updated:
I found that if "Name" = "", then I couldn't get it as string type, I cannot use "data.get("Name");". But I still need it. Anyone can fix it? Thanks.
You can directly convert the response into the POJO/Model class. Check this and this
You don't need manual parsing, if you are using Gson. See how-
List<Response> responseList = new Gson().fromJson(yourJson, new TypeToken<List<Response>>() {
}.getType());
Data data = responseList.get(0).getData();
String id = responseList.get(0).getId();
String date = data.getDate();
String name = data.getName();
Isn't this magic? No manual parsing at all.
Response.java class
public class Response {
private Data data;
private String id;
public void setData(Data data) {
this.data = data;
}
public Data getData() {
return data;
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
Data.java class
public class Data {
private String date;
private String name;
public void setDate(String date) {
this.date = date;
}
public String getDate() {
return date;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
How to generate Pojo classes? So here is several websites jsonschema2pojo. Also many Android Studio plugins available, I use RoboPOJOGenerator.
First of all, your JSON is malformed, it shouldn't have a comma after date.
and to answer your question, don't use map at all.
If you really want to do it without creating a model and additional classes, do it this way:
Gson gson = new Gson();
Type resultType = new TypeToken<List<JsonObject>>() {}.getType();
List<JsonObject> result = gson.fromJson(info, resultType);
System.out.println(result.get(0).get("data").toString());
JsonObject data = result.get(0).get("data").getAsJsonObject();
System.out.println(data.get("Name"));
Getting empty java object while populating the following type of Json.
a.json:
------
{
"queries": [{
"query": {
"id": "q1",
"description": "Fire query to get the Auth token !!"
}
}
],
"executeQuery": ["q2", "q3"]
}
Query.java :
-----------
Note : #Data will take care of creating setter getter by Lombok library.
#Data
public class Query {
#Expose #SerializedName("id")
String id;
#Expose #SerializedName("description")
String description;
}
GRT.java :
----------
#Data
public class GRT{
#Expose #SerializedName("queries")
List<Query> queries ;
#Expose #SerializedName("executeQuery")
List<String> executeQuery;
}
Client call :
----------------------------------------------
private void readJson() throws IOException{
String fileName = "a.json";
// Get Gson object
Gson gson = newGsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
// read JSON file data as String
String fileData = new String(Files.readAllBytes(Paths.get(fileName)));
// parse json string to object
GenericRestTestDefinition grtDef = gson.fromJson(fileData, GenericRestTestDefinition.class);
System.out.println(grtDef.toString());
}
Printing the following :
GRT(queries=[Query(id=null, description=null)], executeQuery=[q2, q3])
Dont know why GRT-> Query Object is not getting populated ????
The proper JSON for this would look like this..
{
"queries":
[
{"id":"q1","description":"Fire query to get the Auth token"},
{"id":"q2","description":"Fire query to get the Auth token 2"}
]
}
public class Test {
public static void main(String[] args) throws Exception {
readJson();
}
private static void readJson() throws IOException {
String json ="{\"queries\":[{\"id\":\"q1\",\"description\":\"Fire query to get the Auth token\"}]}";
// Get Gson object
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
GRT grt = new GRT();
grt.setQueries(Arrays.asList( new Query[]{new Query("q1", "Fire query to get the Auth token")} ));
System.out.println(gson.toJson(grt));
// parse json string to object
GRT grtDef = gson.fromJson(json, new TypeToken<GRT>(){}.getType());
System.out.println(grtDef.queries.get(0));
}
}
If you can't change the json file format you can use this pattern:
#Data
public class GRT{
#Expose #SerializedName("queries")
private List<QueryWrapper> queries = new ArrayList<>();
public List<Query> getQueries() {
return queries.stream().map(it->it.query).collect(Collectors.toList());
}
#Expose #SerializedName("executeQuery")
List<String> executeQuery = new ArrayList<>();
}
#Data
public class QueryWrapper {
#Expose #SerializedName("query")
Query query;
}
#Data
public class Query {
public
#Expose #SerializedName("id")
String id;
#Expose #SerializedName("description")
String description;
}
I am using GSON for the first time. I am trying to deserialise a JSON string into a custom object, but every property of my object is set to null. There are no parsing errors so I think the JSON properties are not mapping to the object?
Here is my code, if anyone could point out where I have gone wrong it would be much appreciated. I have checked everything against tutorial and cannot see the problem. The only thing is that there are more properties in the JSON string than in my object but I hope that does not matter.
JSON string:
{
"_id": "D7D4A7D8219CA25848257C63000A1B50",
"ReportingPerson": "TRAIN2 Ifap",
"InjuredPerson": "TRAIN3 Ifap",
"DateReported": {
"$date": "2014-01-17T00:00:00.000Z"
},
"Company": "test",
"Division": "Learning & Development",
"Site_id": "3CA9AD4E6066388648257B7500047D90",
"Department_id": "724BC4B509E7B61648257363002FD645",
"Area": "Training Room",
"DocNo": "002223",
"CreatedBy": "Ifap TRAIN2",
"DateComposed": {
"$date": "2014-01-17T01:50:23.000Z"
},
"OccurTime": "12:00:00",
"Affiliation": "Employee",
"BriefDescription": "Employee tripped over power lead in computer lab.",
"ThirdPartyInvolvedYN": "No",
"ThirdPartyName": "",
"ThirdPartyAddress": [
""
],
"ThirdPartyTel": "",
"Classification": "Minor Injury",
"Confidential": "",
"ConfidentialMonitors": [
""
],
"IncidentCategory": "2",
"IncidentCategoryPotential": "3",
"ReportableYN": "No",
"ExternalBody": [
""
],
"Authorisor": "",
"WorkSafeConfirmedYN": "No",
"Details": "Fell over cord in computer lab when walking through. Held hand out to brace fall and fell on pinkie finger.",
"Controls": [
"Tape over cord."
],
"Witnesses": [
"No"
],
"Supervisor": "TRAIN1 Ifap",
"IntAuthorisor": "TRAIN3 Ifap",
"IntAuthorisorNext": "",
"AssociatedRisks": {},
"OpenActions": {},
"ClosedActions": {}
}
POJO:
public class Incident {
#SerializedName("_id")
private String _id;
private String docNo;
private String site_id;
private String company;
private String division;
private String department_id;
private Date dateReported;
private String briefDescription;
private String thirdPartyInvolvedYN;
private String supervisor;
private String classification;
private String status;
private String injuredPerson;
private String reportingPerson;
private Date occurDate;
private String occurTime;
//Getters & Setters...
}
Main method:
public Incident convertJSONToBean(String json) {
Incident i = new Incident();
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
Type type = new TypeToken<Incident>(){}.getType();
try {
i = gson.fromJson(json, type);
} catch (JsonSyntaxException e) {
e.printStackTrace();
} catch (JsonIOException e) {
e.printStackTrace();
}
return i;
}
Type is set correctly to Incident.class. But any properties of the resulting Incident object are all null.
I tried commenting out all properties except _id to see if I could get just one to populate but it was still set to null.
In Json format DateComposed & DateReported properties are the Object, You need to create the
either custom model classes for them or write CustomDeserializer class for them.
"DateComposed": { "$date": "2014-01-17T01:50:23.000Z" }
"DateReported": {"$date": "2014-01-17T00:00:00.000Z"}
public class Incident {
#SerializedName("_id")
private String _id;
#SerializedName(value = "ReportingPerson")
// other properties, you need to put #SerializedName on each property
............
// No need to put SerializedName annotation on dateReported & dateComposed
private Date dateReported;
private Date dateComposed;
#SerializedName(value = "ThirdPartyAddress")
private List<String> thirdPartyAddress;
#SerializedName(value = "ConfidentialMonitors")
private List<String> confidentialMonitors;
#SerializedName(value = "ExternalBody")
private List<String> externalBody;
#SerializedName(value = "Controls")
private List<String> controls;
#SerializedName(value = "Witnesses")
private List<String> witnesses;
// getter/setter
....
}
Here is the CustomDeserializer class for Deserializing date properties
public class CustomDeserializer implements JsonDeserializer<Incident> {
#Override
public Incident deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final Gson gson = new Gson();
// Parse the JsonElement tree here
final Incident incident = gson.fromJson(json, Incident.class);
// getting date properties as string from JsonElement and parse them into date object.
String dateReportedStr = jsonObject.get("DateReported").getAsJsonObject().get("$date").getAsString();
String dateComposedStr = jsonObject.get("DateComposed").getAsJsonObject().get("$date").getAsString();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
try {
// setting date properties in incident object
incident.setDateReported(df.parse(dateReportedStr));
incident.setDateComposed(df.parse(dateComposedStr));
} catch (ParseException e) {
e.printStackTrace();
}
return incident;
}
}
Finally Parse it
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Incident.class, new CustomDeserializer());
Gson gson = gsonBuilder.create();
Incident incident = gson.fromJson(Your_JSON_STR, Incident.class);
You have interesting date objects in your json string. Such as;
"DateReported": {
"$date": "2014-01-17T00:00:00.000Z"
}
Which causes a JsonParseException because of your Incident class:
com.google.gson.JsonParseException: The date should be a string value
For your Incident class, Dates at json value should be something like;
"DateReported": "2014-01-17T00:00:00.000Z"
If you don't have an option to change dates at json value, then you should define its custom date holder class:
public class CustomDateHolder {
#SerializedName("$date")
private Date date;
// Getters & Setters...
}
And change those date fields' type to CustomDateHolder;
public class Incident {
#SerializedName("_id")
private String _id;
private String docNo;
private String site_id;
private String company;
private String division;
private String department_id;
private CustomDateHolder dateReported;
private String briefDescription;
private String thirdPartyInvolvedYN;
private String supervisor;
private String classification;
private String status;
private String injuredPerson;
private String reportingPerson;
private CustomDateHolder occurDate;
private String occurTime;
// Getters & Setters...
}
Also modify your GsonBuilder a little bit:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
gsonBuilder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Gson gson = gsonBuilder.create();