Spring Data Mongodb: json string to BasicDBObject - spring

I've created this custom converter:
#Component
#WritingConverter
public class MetadataWriterConverter implements Converter<Metadata, DBObject> {
#Override
public DBObject convert(Metadata metadata) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", metadata.getName());
dbObject.put("metadata", (BasicDBObject) BasicDBObject.parse(reference.getMetadata()));
dbObject.removeField("_class");
return dbObject;
}
}
I'm getting this exception:
Caused by: org.bson.BsonInvalidOperationException: readStartDocument can only be called when CurrentBSONType is DOCUMENT, not when CurrentBSONType is ARRAY.
The problem is on:
(BasicDBObject) BasicDBObject.parse(metadata.getMetadata())
the content of metadata.getMetadata is: "[{'departament': 'JUST'}]".
Metadata class is:
public class Metadata {
private String id;
private String user;
private String metadata;
}
The content of metadata field is a json string, I'm trying to convert to BasicDbObject, but the problem appears when this string is an json array: [{},{}].
Guess:
Metadata met = new Metadata();
met.setId("Mdt1");
met.setUser("user");
met.setMetadata("[{'departament': 'JUST'}]");
What I want to get is:
{
"id": Mdt1,
"user": "user",
"metadata": [{"departament": "JUST"}]
}
Any ideas about how to refactor my converter?

Actually, BasicDBObject.parse() expects a JSONObject instead of a JSONArray that you are passing in your example. Check the docs here - http://api.mongodb.com/java/current/com/mongodb/BasicDBObject.html#parse-java.lang.String-
Instead, you can try converting your reference.getMetadata() into a valid JSON String and then using BasicDBList for your JSONArray. Something like below:
#Component
#WritingConverter
public class MetadataWriterConverter implements Converter<Metadata, DBObject>
{
#Override
public DBObject convert(Metadata metadata) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", metadata.getName());
String jsonString = String.format("{\"data\": " + reference.getMetadata() + "}");
BasicDBObject basicDBObject = (BasicDBObject) BasicDBObject.parse(jsonString);
BasicDBList parsedList = (BasicDBList) basicDBObject.get("data");
dbObject.put("metadata", parsedList);
dbObject.removeField("_class");
return dbObject;
}
}

Related

How to use Jackson for parse object follow json type?

I have two Json objects like :
Object 1
{
"value": {
"data": [
"John",
"Justin",
"Tom"
],
"isGraduated": false
}
}
Object 2
{
"value": {
"data": {
"info": {
"background": {
"primarySchool" : "A school",
"univeristy": "X univeristy"
},
"name": "John",
"gender": "male",
"dayOfBirth": "1995-04-24"
}
},
"isGraduated": false
}
}
How can I deserialize the data field to list of strings or class(I've already declared) by using Jackson?
Edit
Add class Info declaration.
public class Info {
#JsonProperty("background")
private BackGround backGround;
#JsonProperty("name")
private String name;
#JsonProperty("gender")
private String gender;
#JsonProperty("dayOfBirth")
private String dayOfBirth;
public static class BackGround {
#JsonProperty("primarySchool")
private String primarySchool;
#JsonProperty("univeristy")
private String univeristy;
}
}
Looking at your JSON objects, there is no way you can figure out what will be there in data parameter. So you can use JsonNode as type for data parameter.
Note: This is the object hierarchy I have created to represent JSON objects
#ToString
class Wrapper {
private Value value;
// getter & setter
}
#ToString
class Value {
private JsonNode data;
private Boolean isGraduated;
// getter & setter
}
#ToString
class Data {
private Info info;
// getter & setter
}
#ToString
class Info {
private Background background;
private String name;
private String gender;
private String dayOfBirth;
// getter & setter
#ToString
static class Background {
private String primarySchool;
private String univeristy;
// getter & setter
}
}
Then you can check the node type before deserialize between List<String> and Info.calss like this,
JsonNodeType type = value.getValue().getData().getNodeType();
You will see type = JsonNodeType.ARRAY if the json object is type 1 and type = JsonNodeType.OBJECT if the json object is type 2.
Check this exaple,
public class Main {
public static void main(String[] args) throws IOException {
// String s = "{\"value\":{\"data\":[\"John\",\"Justin\",\"Tom\"],\"isGraduated\":false}}";
String s = "{\"value\":{\"data\":{\"info\":{\"background\":{\"primarySchool\":\"A school\",\"univeristy\":\"X univeristy\"},\"name\":\"John\",\"gender\":\"male\",\"dayOfBirth\":\"1995-04-24\"}},\"isGraduated\":false}}";
ObjectMapper om = new ObjectMapper();
Wrapper wrapper = om.readValue(s, Wrapper.class);
JsonNodeType type = wrapper.getValue().getData().getNodeType();
if (type == JsonNodeType.ARRAY) {
List<String> data = om.convertValue(wrapper.getValue().getData(), new TypeReference<List<String>>() {});
System.out.println(data);
} else if (type == JsonNodeType.OBJECT) {
Data data = om.convertValue(wrapper.getValue().getData(), Data.class);
System.out.println(data);
}
}
}
Not the general approach but approach for your specific case
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = (ObjectNode) mapper.readTree(jsonContent);
JsonNode data = root.get("value").get("data");
if (data.has("info")) {
Info result = mapper.convertValue(data.get("info"), Info.class);
// handle result as Info instance
} else {
List<String> result = mapper.convertValue(data, new TypeReference<List<String>>() {});
// handle result as list of strings
}

Cannot use Map as a JSON #RequestParam in Spring REST controller

This controller
#GetMapping("temp")
public String temp(#RequestParam(value = "foo") int foo,
#RequestParam(value = "bar") Map<String, String> bar) {
return "Hello";
}
Produces the following error:
{
"exception": "org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException",
"message": "Failed to convert value of type 'java.lang.String' to required type 'java.util.Map'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found"
}
What I want is to pass some JSON with bar parameter:
http://localhost:8089/temp?foo=7&bar=%7B%22a%22%3A%22b%22%7D, where foo is 7 and bar is {"a":"b"}
Why is Spring not able to do this simple conversion? Note that it works if the map is used as a #RequestBody of a POST request.
Here is the solution that worked:
Just define a custom converter from String to Map as a #Component. Then it will be registered automatically:
#Component
public class StringToMapConverter implements Converter<String, Map<String, String>> {
#Override
public Map<String, Object> convert(String source) {
try {
return new ObjectMapper().readValue(source, new TypeReference<Map<String, String>>() {});
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
}
If you want to use Map<String, String> you have to do the following:
#GetMapping("temp")
public String temp(#RequestParam Map<String, String> blah) {
System.out.println(blah.get("a"));
return "Hello";
}
And the URL for this is: http://localhost:8080/temp?a=b
With Map<String, String>you will have access to all your URL provided Request Params, so you can add ?c=d and access the value in your controller with blah.get("c");
For more information have a look at: http://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-mvc-request-param/ at section Using Map with #RequestParam for multiple params
Update 1: If you want to pass a JSON as String you can try the following:
If you want to map the JSON you need to define a corresponding Java Object, so for your example try it with the entity:
public class YourObject {
private String a;
// getter, setter and NoArgsConstructor
}
Then make use of Jackson's ObjectMapper to map the JSON string to a Java entity:
#GetMapping("temp")
public String temp(#RequestParam Map<String, String> blah) {
YourObject yourObject =
new ObjectMapper().readValue(blah.get("bar"),
YourObject.class);
return "Hello";
}
For further information/different approaches have a look at: JSON parameter in spring MVC controller

Query MongoDb based on Map Key Spring Repository

I need help to query nested documents. Using Spring Boot with MongoDB.
Structure:
public class Holiday {
#Id
private String id;
private Integer year;
private Map<String, List<HolidayElement>> holidays = new HashMap<>();
}
public class HolidayElement {
private String name;
#JsonFormat(pattern="yyyy-MM-dd")
private Date date;
private String note;
}
After saving everything the Json looks like:
[
{
"id": "5a153331b3cb1f0001e1edeb",
"year": 2017,
"holidays": {
"BB": [
{
"name": "Neujahrstag",
"date": "2017-01-01",
"note": ""
},
...
],
"HH": [
{ ... }
]
}
]
Now how can I get for instance: List of "HolidayElement" where the State is "BB"?
Assuming you have a repository like HolidayRepository, you need to create a custom implementation since you want to use MongoTemplate. So your HolidayRepository will look like
#Repository
public interface HolidayRepository extends MongoRepository<Holiday, String>, HolidayRepositoryCustom {
}
And declare two new files HolidayRepositoryCustom and HolidayRepositoryImpl in the same directory(very important) as HolidayRepository
public interface HolidayRepositoryCustom {
List<HolidayElement> findByMapId(final String mapId);
}
And the Impl class will look like this
public class HolidayRepositoryImpl implements HolidayRepositoryCustom {
private final MongoTemplate mongoTemplate;
#Autowired
public HolidayRepositoryImpl(final MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public List<HolidayElement> findByMapId(String mapId) {
final QueryBuilder queryBuilder = QueryBuilder.start();
queryBuilder
.and("holidays."+mapId).exists(true);
final DBObject projection = new BasicDBObject();
projection.put("holidays."+mapId, 1);
String collectionName = "Holiday";//Change to your collection name
try( final DBCursor dbCursor = mongoTemplate.getCollection(collectionName).find(queryBuilder.get(), projection)){
if(dbCursor.hasNext()){
DBObject next = dbCursor.next();
Map<String, List<HolidayElement>> holidayElements =
(Map<String, List<HolidayElement>>) next.get("holidays");
return holidayElements.get(mapId);
}
}
return Lists.newArrayList();
}
}

How to convert a Object with enum attribute in Spring Data MongoDB?

I have the following class that I want to save and query from MongoDB. I managed to set a custom convert that converts from InstanceType to String and String to InstanceType. InstanceType is a custom enum that will be stored in a different way!
#Document
public class Instance {
private String name;
private InstanceType type;
private List<Configuration> configurations;
private Map<String, String> properties;
}
MongoConfiguration class
#Bean
#Override
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new InstanceTypeToStringConverter());
converters.add(new StringToInstanceTypeConverter());
return new CustomConversions(converters);
}
#Bean
#Override
public MappingMongoConverter mappingMongoConverter() throws Exception {
MongoMappingContext mappingContext = new MongoMappingContext();
DbRefResolver databaseResolver = new DefaultDbRefResolver(mongoFactory());
MappingMongoConverter mongoConverter = new MappingMongoConverter(databaseResolver, mappingContext);
mongoConverter.setCustomConversions(customConversions());
mongoConverter.afterPropertiesSet();
return mongoConverter;
}
The problem I'm having is that when I try to query the Instance object in MongoDB, Spring uses StringToInstanceType to convert "name" attribute. Why Spring is using this converter? This converter should not only be applied to "type" attribute?
This is the query I'm trying to execute:
public Instance getInstance(InstanceType type, String name) {
return mongoTemplate.findOne(Query.query(
Criteria.where("type").is(type)
.and("name").is(name)
), Instance.class, COLLECTION_INSTANCES);
}
Type attribute is converted using InstanceTypeToStringConverter (Thats OK because the source is InstanceType and target is String)
Name attribute is converted using StringToInstanceTypeConverter (It's wrong because the source is String and target is a String)
Because os this behavior, the query is returning NULL.
Let's say I have the follow object in MongoDB:
{"type": "user", "name": "Sandro"}
InstanceType.USER is converted to "user" (InstanceTypeToStringConverter).
"Sandro" is converted to null (StringToInstanceTypeConverter) because InstanceType.getInstanceType("Sandro") returns null.
In this case Spring will query mongo this way:
Where type = "user" and name = null

How to de-serialize POJO contains HashTable?

I have pojo like this:
public class Test implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private String hash;
private java.util.Hashtable<Integer, Long> myTempTable;
public java.util.Hashtable<Integer, Long> getMyTempTable() {
return this.myTempTable;
}
public void setMyTempTable(java.util.Hashtable<Integer, Long> myTempTable) { this.myTempTable = myTempTable; }
//And some few variables
}
In response I get this POJO in JSON format but while converting this JSON to "Test" java object like this.
gson.fromJson(tempString, Test.class);
It is giving error as
java.lang.IllegalArgumentException: Can not set java.util.Hashtable field <package_name>.Temp.myTempTable to java.util.LinkedHashMap
Why GSON is converting HashTable to LinkedHashMap?
And does this error means?
UPDATE: JSON File as
{
"hash": "abc",
"myTempTable": {
"1": 30065833999,
"2": 34364325903,
"3": 536872959
}
}
For converting an Object to JSON String.
public static <T> String convertObjectToStringJson(T someObject, Type type) {
Gson mGson = new Gson();
String strJson = mGson.toJson(someObject, type);
return strJson;
}
For converting a JSON String to an Object.
public static <T> T getObjectFromJson(String json, Type type) {
Gson mGson = new Gson();
if (json != null) {
if (json.isEmpty()) {
return null;
}
}
return mGson.fromJson(json, type);
}
where
Type is type of your Object.
ex:
for object:
new TypeToken<YOUR_POJO>(){}.getType();
for list:
new TypeToken<List<YOUR_POJO>>(){}.getType();

Resources