I am trying to insert a whole Java object into a MongoDB Collection using Java. I am getting following error:
Error :
Exception in thread "main" java.lang.IllegalArgumentException: can't serialize class net.yogesh.test.Employee
at org.bson.BSONEncoder._putObjectField(BSONEncoder.java:185)
at org.bson.BSONEncoder.putObject(BSONEncoder.java:119)
at org.bson.BSONEncoder.putObject(BSONEncoder.java:65)
at com.mongodb.DBApiLayer$MyCollection.insert(DBApiLayer.java:176)
at com.mongodb.DBApiLayer$MyCollection.insert(DBApiLayer.java:134)
at com.mongodb.DBApiLayer$MyCollection.insert(DBApiLayer.java:129)
at com.mongodb.DBCollection.save(DBCollection.java:418)
at net.yogesh.test.test.main(test.java:31)
Emplyoee.java (POJO)
package net.yogesh.test;
import java.io.Serializable;
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private long no;
private String name;
public Employee() {
}
public long getNo() {
return no;
}
public void setNo(long no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Main Method Class (test.java)
package net.yogesh.test;
import java.net.UnknownHostException;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
public class test {
public static void main(String[] args) throws UnknownHostException,
MongoException {
Mongo mongo = new Mongo("localhost", 27017);
DB db = mongo.getDB("test");
Employee employee = new Employee();
employee.setNo(1L);
employee.setName("yogesh");
BasicDBObject basicDBObject = new BasicDBObject("Name", employee);
DBCollection dbCollection = db.getCollection("NameColl");
dbCollection.save(basicDBObject);
}
}
Can anybody explain why I am getting this error?
I'm a little confused as to know why you'd think this would work in the first place. The first thing you need to know is how to map your POJO to a MongoDB document. Currently, you're not telling the system(your code) how to do that.
You can either use a mapping library for this (Morphia comes to mind) or use ReflectionDBObject. Either solution allows you to map POJO to MongoDB document or MongoDB document to POJO(the former way is a lot more nicely than the latter).
DB db = mongoClient.getDB( "mydb" );
coll = db.getCollection("testCollection");
Employee emp = new Employee();
emp.setId("1001");
emp.setName("John Doe");
//Converting a custom Class(Employee) to BasicDBObject
Gson gson = new Gson();
BasicDBObject obj = (BasicDBObject)JSON.parse(gson.toJson(emp));
coll.insert(obj);
findEmployee(new BasicDBObject("id","1001"));
public static void findEmployee(BasicDBObject query){
DBCursor cursor = coll.find(query);
try {
while(cursor.hasNext()) {
DBObject dbobj = cursor.next();
//Converting BasicDBObject to a custom Class(Employee)
Employee emp = (new Gson()).fromJson(dbobj.toString(), Employee.class);
System.out.println(emp.getName());
}
} finally {
cursor.close();
}
}
I thought that it would be useful to post code that did conversions both ways.
Storing an Employee Object
Finding and re-creating an employee Object
Hope this is useful..
Pro
you continue to work with strong typed objects as you wanted to
Contra
Some people really dislike : extends
package foo;
import com.mongodb.BasicDBObject;
public class Employee extends BasicDBObject {
private static final long serialVersionUID = 2105061907470199595L;
//should be something shorter as "name" like "n"
//here just use name to conform your sample
public static final String NAME = "name";
public static final String NO = "no";
public static final String COLLECTION_NAME = "employee";
public Long getNo() {
return getLong(NO);
}
public void setNo(long no) {
put(NO, no);
}
public String getName() {
return getString(NAME);
}
public void setName(String name) {
put(NAME, name);
}
}
package foo;
import java.net.UnknownHostException;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
public class Test {
public static void main(String[] args) throws UnknownHostException,
MongoException {
Mongo mongo = new Mongo("localhost", 27017);
DB db = mongo.getDB("yeahMongo");
Employee employee = new Employee();
employee.setNo(1L);
employee.setName("yogesh");
DBCollection employeeCollection = null ;
employeeCollection = db.getCollection(Employee.COLLECTION_NAME);
employeeCollection.save(employee);
System.err.println(employeeCollection.findOne());
}
}
In addition to morphia you should take a look to
jongo :
http://jongo.org/
jongo use the same form syntax as js mongo engine, and I found it great point for a beginner. You don't have to switch your mental map between mongojs and java. you can use the js sample with little changes.
You can convert your java object into json string using the gson library and then insert it in mongodb.
Eg:
Gson gson = new Gson();
String json = gson.toJson(Employee);
BasicDBObject basicDBObject = new BasicDBObject("Name", json );
DBCollection dbCollection = db.getCollection("NameColl");
dbCollection.save(basicDBObject);
There have been several changes since this question was asked. Using test.java in the question, here is what worked for me using Google's Gson:
import com.google.gson.Gson;
import com.mongodb.Block;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
public class test {
public static void main(String[] args) {
MongoClient mongoClient = new MongoClient(); // Connect with default settings i.e. localhost:27017
MongoDatabase db = mongoClient.getDatabase("test"); // Get database "test". Creates one if it doesn't exist
Employee employee = new Employee(); // Create java object
employee.setNo(1L);
employee.setName("yogesh");
// Deserialize object to json string
Gson gson = new Gson();
String json = gson.toJson(employee);
// Parse to bson document and insert
Document doc = Document.parse(json);
db.getCollection("NameColl").insertOne(doc);
// Retrieve to ensure object was inserted
FindIterable<Document> iterable = db.getCollection("NameColl").find();
iterable.forEach(new Block<Document>() {
#Override
public void apply(final Document document) {
System.out.println(document); // See below to convert document back to Employee
}
});
}
}
You can also use Gson to convert retrieved bson document back to Java object:
Gson gson = new Gson();
Employee emp = gson.fromJson(document.toJson(), Employee.class);
With MongoDB you cannot insert your Java bean in the DB, but you have to remap them to MongoDB Object.
In your case you have to do:
BasicDBObject basicDBObject = new BasicDBObject();
basicDBObject.put("no", employee.getNo());
basicDBObject.put("name", employee.getName());
Hopefully this will work for you and you can get help from it.
I performed database operations(insert,delete, update, get, getall) and used Person object for operations in MongoDB with java for demo purpose.
Database connection class
Connection.java
package test;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.client.MongoDatabase;
public class Connection {
public MongoClient mongo;
private String db;
public MongoDatabase database;
private static Connection instance;
private Connection() {
db = "chatsystem";
CodecRegistry pojoCodecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
mongo = new MongoClient("localhost", MongoClientOptions.builder().codecRegistry(pojoCodecRegistry).build());
database = mongo.getDatabase(db);
}
public static Connection getInstance() {
if (instance == null) {
instance = new Connection();
return instance;
} else {
return instance;
}
}
}
Model Class
Person.java
package test;
import org.bson.types.ObjectId;
public class Person {
public Person() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public String getLname() {
return lname;
}
public void setLname(String lname) {
this.lname = lname;
}
private ObjectId id;
public Person(String username, String email, String password, String fname, String lname) {
super();
this.username = username;
this.email = email;
this.password = password;
this.fname = fname;
this.lname = lname;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
private String username;
private String email;
private String password;
private String fname;
private String lname;
}
Main class
test.java
package test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static com.mongodb.client.model.Filters.*;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
public class test {
private MongoCollection<Person> person;
Connection conn;
public void getCollection() {
conn = Connection.getInstance();
person = conn.database.getCollection("person", Person.class);
}
public static void main(String[] args) throws Exception {
test t = new test();
t.getCollection();
Person p = new Person();
p.setEmail("test#test.com");
p.setFname("ftest");
p.setLname("ltest");
p.setPassword("testtest");
p.setUsername("test123");
// insert person type objects in database
t.insertPerson(p);
// get all persons from database
List<Person> pp = t.getAllPersons();
Person pt = pp.get(0);
System.out.println(pt.getEmail());
System.out.println(pt.getId());
// get one person from database by username filter
// pass username of person in method argument
Person ph = t.getOnePerson("test123");
System.out.println(ph.getEmail());
System.out.println(ph.getId());
// update/edit person by username filter
// pass username of person in method argument
t.updatePerson("test123");
// delete person by username filter
// pass username of person in method argument
t.removePerson("updatetest123");
}
public void insertPerson(Person p) {
person.insertOne(p);
}
public List<Person> getAllPersons() {
FindIterable<Person> iterable = person.find();
Iterator it = iterable.iterator();
List<Person> allPersons = new ArrayList<>();
while (it.hasNext()) {
Person per = (Person) it.next();
allPersons.add(per);
}
return allPersons;
}
public Person getOnePerson(String username) {
return person.find(eq("username", username)).first();
}
public void updatePerson(String username) {
Person p = new Person();
p.setEmail("update#test.com");
p.setFname("updateftest");
p.setLname("updateltest");
p.setPassword("updatetesttest");
p.setUsername("updatetest123");
person.replaceOne(eq("username", username), p);
}
public void removePerson(String username) {
person.deleteOne(eq("username", username));
}
}
Highly recommend MongoJack, a decent library to map Java objects to/from MongoDB documents.
The code would be something like below:
import java.util.Arrays;
import org.mongojack.JacksonDBCollection;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
public class Test {
public static void main(String[] args) {
MongoClient mongoClient = new MongoClient(Arrays.asList(new ServerAddress("localhost", 27017)));
DB db = mongoClient.getDB("test");
Employee employee = new Employee();
employee.setNo(1L);
employee.setName("yogesh");
JacksonDBCollection<Employee, String> collectionData = JacksonDBCollection.wrap(db.getCollection("NameColl"), Employee.class, String.class);
collectionData.save(employee);
mongoClient.close();
}
}
(PS: Currently I'm using mongo-java-driver v3.2.2, and mongojack v2.6.1)
Use BasicDBObjectBuilder to convert your POJO to an instance of DBObject which a DBCollection can save:
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
public class Employee {
private long no;
private String name;
// Getters and Setters
public DBObject toDBObject() {
BasicDBObjectBuilder builder = BasicDBObjectBuilder
.start("no", no)
.append("name", name);
return builder.get();
}
}
In order to save, just call toDBObject() on POJO instance and provide it to collection:
public class test {
public static void main(String[] args) throws UnknownHostException,
MongoException {
...
DBCollection dbCollection = db.getCollection("NameColl");
Employee employee = new Employee();
employee.setNo(1L);
employee.setName("yogesh");
dbCollection.save(employee.toDBObject());
}
}
Using this approach:
You don't need to manually create a DBObject every time
You don't need to mess up your POJO by extending Mongo classes (what if your POJO is already extending a class?)
You don't need a Json mapper [and its annotations on POJO fields]
You only have dependency to java-mongo-driver jar
I have the same error, when I try to insert a java BasicDBObject into a MongoDb Collection.
My object is created from a Xml converted to Json.
java.lang.IllegalArgumentException: can't serialize class net.sf.json.JSONNull
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:299)
at org.bson.BasicBSONEncoder.putMap(BasicBSONEncoder.java:339)
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:261)
This error is caused by empty tags in Xml; when I removed all empty tags, then I solved it.
Just use "insertOne" method, not save.
MongoCollection collection;
String collectionName = "somename";
String jsonObject = "{}";
if (!mongoTemplate.collectionExists(collectionName)) {
collection = mongoTemplate.createCollection(collectionName);
logger.info("Collection %s was successfully created", collectionName);
} else {
collection = mongoTemplate.getCollection(collectionName);
}
collection.insertOne(Document.parse(jsonObject));
Since nobody has mentioned it - I think there might be a solution to this using bson4jackson. This bills itself as a fast encoder.
Related
I'm developing a springboot application.
I've a class with the following fields
class MyClass
{
String s1;
String s2;
String s3;
String s4;
//getters setters constructors
}
I'm calling an API which in turn calls a service.
public String myService()
{
JSONArray arr1 = new JSONArray();
for (Items item : itemsList)
{
JSONObject itemObj = new JSONObject();
itemObj.put("s1","Value1");
itemObj.put("s2","Value2");
itemObj.put("s3","Value3")
itemObj.put("s4","Value4");
arr1.put(itemObj);
}
JSONObject out = new JSONObject();
out.put("total_Items", arr1);
return out.toString(); // this is org.json.JSONObject
}
This way I'm able to get the excel with the reordered members as the columns when a button is clicked at the frontend angular app.
What I want is the order of the the members in the columns remains preserved when exporting into an excel sheet.
s1|s2|s3|s4 //as per the above example
I've many other services as well, which return different types of Objects(apart from the MyClass mentioned here) so I wanted to return the elements in the order defined (as per the order of members in the class) from the backend itself.
I know that JSON does not allow us to preserve the order as it internally uses a HASHMAP.
Is there a way to return a JSON response such that the order remains same as that of the class members?
I also tried using GSON in the below way.
public String myService()
{
MyClass itemsArray[] = new MyClass[itemsList.size()];
int i=0;
for (Items item : itemsList)
{
MyClass itemObj = new MyClass();
itemObj.setS1("Value1");
itemObj.setS2("Value2");
itemObj.setS3("Value3")
itemObj.setS4("Value4");
itemsArray[i]=itemObj;
}
Gson gson = new Gson();
return gson.toJson(itemsArray); // this is java.lang.String
}
In this scenario I'm able to get the API response(on POSTMAN) with the elements in ordered fashion but when exporting on the frontend angular app it freezes at the downloading screen.
I tried doing conversion to JSONObject and other things randomly but was not able to make the code work properly.
Is there anyway the problem can be resolved at the backend...or something needs to be done at the frontend only?
Thanks in advance.
If you want to do using DataStructure use LinkedHashMap as given below. It will serialize in inserted order.
public static void main(String args[]){
ObjectMapper mapper = new ObjectMapper();
Map<String, String> itemObj = new LinkedHashMap<>();
itemObj.put("s91","Value1");
itemObj.put("s2","Value2");
itemObj.put("s3","Value3");
itemObj.put("s4","Value4");
try {
String jsonString = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(itemObj);
System.out.println(jsonString);
}
catch (IOException e) {
e.printStackTrace();
}
}
{
"s91" : "Value1",
"s2" : "Value2",
"s3" : "Value3",
"s4" : "Value4"
}
If you want to serialize in sorted order the use TreeMap, it will serialize in sorted key
Map<String, String> itemObj = new TreeMap<>();
...
{
"s2" : "Value2",
"s3" : "Value3",
"s4" : "Value4",
"s91" : "Value1"
}
This can be done using Jackson library using the #JsonPropertyOrder annotation.
You can define the order of elements as given below above class
#JsonPropertyOrder({ "s3", "s2", "s1", "s4"})
Refer to below a working example
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.istack.NotNull;
import java.io.IOException;
import java.util.UUID;
#JsonPropertyOrder({ "id", "password", "name", "email", "enabled" })
public class UserResource {
private UUID id;
#NotNull
private String name;
#NotNull
private String email;
private boolean enabled;
private String password;
public UserResource(UUID id, String name, String email, boolean enabled, String password) {
this.id = id;
this.name = name;
this.email = email;
this.enabled = enabled;
this.password = password;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public static void main(String args[]){
ObjectMapper mapper = new ObjectMapper();
try {
UserResource student = new UserResource(UUID.randomUUID(), "sheel", "sheel#c4c.com",true, "$$$$$$%%##^^$DSGHHH");
String jsonString = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(student);
System.out.println(jsonString);
}
catch (IOException e) {
e.printStackTrace();
}
}
}
and output as given below
{
"id" : "fbfcd21d-731e-4acb-9fec-90a499e47cc9",
"password" : "$$$$$$%%##^^$DSGHHH",
"name" : "sheel",
"email" : "sheel#c4c.com",
"enabled" : true
}
I have a need to select only one field name (categoryName) from my collection matching my criteria. Instead all fields are returned from the collection for the following code:
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
public ServiceList findOneByCategoryName(String categoryName) {
// TODO Auto-generated method stub
Query query = new Query() ;
query.addCriteria(Criteria.where("categoryName").is(categoryName));
return (ServiceList) mongoTemplate.findOne(query,ServiceList.class);
}
ServiceList.java
#Document(collection = "services")
public class ServiceList {
#Id
private String id;
#Field("categoryName")
#JsonProperty("categoryName")
private String categoryName;
#Field("serviceTypes")
#JsonProperty("Service Types")
private List<ServiceType> serviceTypes;
public String getMfServicesId() {
return id;
}
public void setMfServicesId(String mfServicesId) {
this.id = mfServicesId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public List<ServiceType> getServiceTypes() {
return serviceTypes;
}
public void setServiceTypes(List<ServiceType> serviceTypes) {
this.serviceTypes = serviceTypes;
}
}
For this code, all fields are returned from the collection. I don't know how to select specific field that i chose to display.
I think this might work, using the fields() method:
Query query = new Query();
query.addCriteria(Criteria.where("categoryName").is(categoryName));
query.fields().include("categoryName");
query.fields().exclude("id"); // Not sure you have to exclude Id explicitly
return (String) mongoTemplate.findOne(query,String.class);
Following code worked to select and display a specific column from a document using include function.
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
public ServiceList findOneByCategoryName(String categoryName) {
Query query = new Query() ;
query.addCriteria(Criteria.where("categoryName").is(categoryName));
query.fields().include("categoryName");
return (ServiceList) mongoTemplate.findOne(query,ServiceList.class);
}
public class User {
String name;
String id;
Address[] address;
....
....
//40 more fields
}
public class Address {
String street;
String city;
String state;
}
I have a List and I need to convert it to json with only few fields.
public String fetchUsers(List<Users> users, List<String> fields) {
//fetch the list of users having the specific fields in the list and return as json
}
fields = ["name", "address.state"]
I can remove fields in json.... But, I need to keep restricted fields as per values passed in the method. I can use any third party lib as well.
Use com.google.gson.Gson library for serialize your objects into json and you have to create ExclusionStrategy for your object for fields which you want to skip or not. create a GsonBuilder object from that and parse your object from it.
this is perfectly working fine.
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ABC {
public class User {
public String name;
public String id;
public Address[] address;
}
public class Address {
public String street;
public String city;
public String state;
}
public static ExclusionStrategy createStrategy(List<String> fields) {
return new ExclusionStrategy() {
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
if (fields.stream().anyMatch(e -> e.equals(fieldAttributes.getName()))) {
return false;
}
return true;
}
public boolean shouldSkipClass(Class aClass) {
return false;
}
};
}
public String fetchUsers(List<User> users, List<String> fields) {
GsonBuilder builder = new GsonBuilder();
builder.setExclusionStrategies(createStrategy(fields));
Gson gson = builder.create();
return gson.toJson(users);
}
public static void main(String[] args) {
ABC x = new ABC();
Address add = x.new Address();
add.city = "city";
add.state = "state";
add.street = "street";
Address[] array = new Address[1];
array[0] = add;
User user = x.new User();
user.address = array;
user.id = "id";
user.name = "name";
List<User> users = new ArrayList<>();
users.add(user);
List<String> fields = Arrays.asList("name", "address", "state");
String json = x.fetchUsers(users, fields);
System.out.println(json);
}
}
and output of this code is :
[{"name":"name","address":[{"state":"state"}]}]
and dependency for Gson is.
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
I am trying to obtain a value from a parent node using Jackson.
I know this is possible to achieve with custom deserialisers, but then there is too much boilerplate because you suddenly have to handle everything manually.
It sounds like something quite simple but didn't find a way to do it.
To illustrate what I want - If we have a simple User with Address...
#JsonDeserialize(builder = User.Builder.class)
public class User
{
private long id;
private String firstName;
private Address address;
...
public static class Builder
{
public Builder withId(long id);
public Builder withFirstName(String value);
public Builder withAddress(Address address);
public User create();
}
}
If we have the same for address
#JsonDeserialize(builder = Address.Builder.class)
public class Address
{
...
public static class Builder
{
public Builder withUserId(long id); // is there a way to ask for the parent id here?
public Builder withStreetName(String value);
public Address create();
}
}
Sample input:
{
"id": 7,
"firstName" : "John",
"lastName" : "Smith",
"address" : {
"streetName": "1 str"
}
}
No, I don't think you can with any of the existing Jackson code. The only thing I believe that can cross parent/child relationships like that is type serialization/deserialization and the UNWRAP_ROOT_VALUE support.
If you want something like that, you'd either need to use a custom deserializer for User, or customize the User constructor to build a new address with the correct UserId before adding it to the builder's internal state. Here's an example (using Lombok to handle the boilerplate generation of builders):
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.experimental.Wither;
public class Scratch {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1234,\"address\":{\"street\":\"123 Main St.\"}}";
User user = mapper.readValue(json, User.class);
System.out.println(user.toString());
}
#Value
#JsonDeserialize(builder = User.UserBuilder.class)
public static class User {
private final int id;
private final Address address;
#Builder(toBuilder = true)
public User(int id, Address address) {
this.id = id;
// Build a new address with the user's ID
this.address = address.withUserId(id);
}
#JsonPOJOBuilder(withPrefix = "")
public static class UserBuilder {}
}
#Value
#Builder(toBuilder = true)
#JsonDeserialize(builder = Address.AddressBuilder.class)
public static class Address {
#Wither
private final int userId;
private final String street;
#JsonPOJOBuilder(withPrefix = "")
public static class AddressBuilder {}
}
}
This consumes the following json:
{
"id": 1234,
"address": {
"street": "123 Main St."
}
}
and produces the following output:
Scratch.User(id=1234, address=Scratch.Address(userId=1234, street=123 Main St.))
I haven't worked with JSON data before, thus the question.
I've the following JSON object in a file.
{
"courses": [
{ "id":998", "name":"Java Data Structures", "teacherId":"375" },
{ "id":"999", "name":"Java Generics", "teacherId":"376" }
],
"teachers": [
{ "id":"375", "firstName":"Amiyo", "lastName":"Bagchi"},
{ "id":"376", "firstName":"Dennis", "lastName":"Ritchie"}
]
}
Here are my model Objects.
public class Course {
private int _id;
private String _name;
private Teacher _teacher;
}
public class Teacher {
private int _id;
private String _firstName;
private String _lastName;
}
My task is to read the JSON Objects and return a list of Model objects.
I've imported the simple.JSON family of jar and here's my code that reads the file.
FileReader reader = new FileReader(path);
JSONParser parser = new JSONParser();
Object obj = parser.parse(reader);
JSONObject jsonObject = (JSONObject) obj;
My question is,
How do I parse the JSON document into my Model objects?
If the input file is JSON but of a different format how do I throw exception/handle the anomaly?
Any help appreciated.
UPDATE I suggest you use JSON parser to parse the data:
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
class Course {
public int _id;
public String _name;
public Teacher _teacher;
private Course(int id, String name, Teacher teacher){
this._id = id;
this._name = name;
this._teacher = teacher;
}
public Course() {
}
}
class Teacher {
public int _id;
public String _firstName;
public String _lastName;
private Teacher(int id, String fname, String lname){
this._id = id;
this._firstName = fname;
this._lastName = lname;
}
public Teacher(){
}
}
public class jsontest {
public static void main(String[] args) throws JSONException, IOException {
// String JSON_DATA = "{\n"+
// " \"courses\": [\n"+
// " { \"id\":\"998\", \"name\":\"Java Data Structures\", \"teacherId\":\"375\" },\n"+
// " { \"id\":\"999\", \"name\":\"Java Generics\", \"teacherId\":\"376\" }\n"+
// "\n"+
// " ],\n"+
// " \"teachers\": [\n"+
// " { \"id\":\"375\", \"firstName\":\"Amiyo\", \"lastName\":\"Bagchi\"},\n"+
// " { \"id\":\"376\", \"firstName\":\"Dennis\", \"lastName\":\"Ritchie\"} \n"+
// " ]\n"+
// "}\n"+
// "";
// read json file into string
String JSON_DATA = new String(Files.readAllBytes(Paths.get("path_to_json_file")), StandardCharsets.UTF_8);
// using a JSON parser
JSONObject obj = new JSONObject(JSON_DATA);
// parse "teachers" first
List<Teacher> listCourses = new ArrayList<Teacher>();
List<JSONObject> listObjs = parseJsonData(obj,"teachers");
for (JSONObject c: listObjs) {
Teacher teacher = new Teacher();
teacher._id = c.getInt("id");
teacher._firstName = c.getString("firstName");
teacher._lastName = c.getString("lastName");
listCourses.add(teacher);
}
// parse "courses" next
List<Course> resultCourses = new ArrayList<Course>();
List<JSONObject> listObjs2 = parseJsonData(obj, "courses");
for (JSONObject c: listObjs2) {
Course course = new Course();
course._id = c.getInt("id");
course._name = c.getString("name");
int teacherId = c.getInt("teacherId");
HashMap<String, Teacher> map = new HashMap<String, Teacher>();
for (Teacher t: listCourses){
map.put(Integer.toString(t._id), t);
}
course._teacher = map.get(Integer.toString(teacherId));
resultCourses.add(course);
}
}
public static List<JSONObject> parseJsonData(JSONObject obj, String pattern)throws JSONException {
List<JSONObject> listObjs = new ArrayList<JSONObject>();
JSONArray geodata = obj.getJSONArray (pattern);
for (int i = 0; i < geodata.length(); ++i) {
final JSONObject site = geodata.getJSONObject(i);
listObjs.add(site);
}
return listObjs;
}
}
Output:
BTW: The json data in the example has one value whose double quotes are not in pairs. To proceed, it must be fixed.
You should try using Jackson as the JSON parsing library instead. There is a lot more support and features that come with it.
In your case, a couple of annotations to map the JSON properties to the Java fields should be sufficient.
https://github.com/FasterXML/jackson-annotations
https://github.com/FasterXML/jackson-databind
UPDATE: Some code, to show just much better this can be done with Jackson.
public class Course {
#JsonProperty("id")
private int _id;
#JsonProperty("name")
private String _name;
#JsonProperty("teacher")
private Teacher _teacher;
// ...public getters and setters
}
public class Teacher {
#JsonProperty("id")
private int _id;
#JsonProperty("firstName")
private String _firstName;
#JsonProperty("lastName")
private String _lastName;
// ...public getters and setters
}
// Container class to conform to JSON structure
public class CoursesDto {
private List<Teacher> teachers;
private List<Course> courses;
}
// In your parser place
ObjectMapper mapper = new ObjectMapper();
FileReader reader = new FileReader(path);
CoursesDto dto = mapper.readValue(reader, CoursesDto.class);
The #JsonProperty annotations tell Jackson what JSON key should be used to deserialize. They are not necessary if the property names match the JSON keys. That means that if you remove the leading underscore from your property names, this would work without annotations. Also, Jackson will default to using public fields and getter/setter methods. This means that you can keep your fields prefixed by _ as long as the getter/setter don't have it (setFirstName(String firstName)).