I am trying to convert the following object to query string, so that can be used with GET request.
Class A {
String prop1;
String prop2;
Date date1;
Date date2;
ClassB objB;
}
Class B {
String prop3;
String prop4;
}
We can do that first object to Map then convert map to MultiValueMap and use URIComponentsBuilder.fromHttpUrl("httpL//example.com").queryParams(multiValueMap).build();
Is there shorter and better way of converting object to query string so that be used with GET request in Spring Project for Junit Test?
Why convert to Map then MultiValueMap, instead of just building it directly?
DateFormat dateFmt = new SimpleDateFormat("whatever date format you want");
URIComponentsBuilder.fromHttpUrl("httpL//example.com")
.queryParam("prop1", a.prop1)
.queryParam("prop2", a.prop2)
.queryParam("date1", dateFmt.format(a.date1))
.queryParam("date2", dateFmt.format(a.date2))
.queryParam("prop3", a.objB.prop3)
.queryParam("prop4", a.objB.prop4)
.build();
You could write your own method that uses java.lang.reflect. Here's an example
public static String getRequestString(String urlString, Class clazz, Object o){
String queryString = "?";
try {
for (Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
queryString += queryString.concat(f.getName() + "=" + String.valueOf(f.get(o)) + "&");
}
}catch (Exception e){
e.printStackTrace();
}
return urlString + queryString.substring(0,queryString.length()-1);
}
OpenFeign has the annotation #QueryMap to generate query params dinamicaly based on a object attributes:
public interface Api {
#RequestLine("GET /find")
V find(#QueryMap CustomPojo customPojo);
}
See more at:
https://github.com/OpenFeign/feign#dynamic-query-parameters
This is how i would do it,
Create Map, populate and then iterate over map items and append to builder this seems to be working for me. It does not cover support for nested objects. Should be simple with recursion.
public String getRequestString(Class clazz, Object o) {
StringBuilder queryStringBuilder = new StringBuilder();
final Map<String, String> queryParams = new LinkedHashMap<>();
try {
for (Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
queryParams.put(f.getName(), String.valueOf(f.get(o)));
}
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
queryStringBuilder.append(testStringUtils.toSnakeCase(entry.getKey()));
queryStringBuilder.append("=");
queryStringBuilder.append(entry.getValue());
queryStringBuilder.append("&");
}
logger.info("Map: " + queryParams);
} catch (Exception e) {
e.printStackTrace();
}
final String queryString = queryStringBuilder.toString();
logger.info("Query string : " + queryString.substring(0, queryString.length() - 1));
return "?" + queryString.substring(0, queryString.length() - 1);
Related
I need to load the data to the elasticsearch index. I am using BULK API of elasticsearch to load the JSONs to index.
private String FOLDER_PATH = "src/main/resources/allJsons";
private String index = "test1";
private static final String TYPE = "test_type";
#Autowired
private RestHighLevelClient restHighLevelClient;
public String loadBulkData() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
AtomicInteger counter = new AtomicInteger();
try (Stream<Path> filePathStream = Files.walk(Paths.get(FOLDER_PATH))) {
filePathStream.forEach(filePath -> {
if (Files.isRegularFile(filePath)) {
counter.getAndIncrement();
try {
String content = Files.readString(filePath);
JSONObject jsonObject1 = new JSONObject(content);
HashMap yourHashMap1 = new Gson().fromJson(jsonObject1.toString(), HashMap.class);
IndexRequest indexRequest = new IndexRequest(index, TYPE).source(yourHashMap1);
bulkRequest.add(indexRequest);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
try {
restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return "Bulk data loaded to index " + index + "";
}
}
I have multiple JSONs based on the following format
[
{
"Nutrient" : "Calories",
"Amount" : " 289.00",
"Unit" : " kcal"
}, {
"Nutrient" : "Fat",
"Amount" : " 17.35",
"Unit" : " g"
}
]
While running the code it gives me error ,
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.json.JSONException: A JSONObject text must begin with '{' at 1 [character 2 line 1]
I think the data is in JSONArray and for the code, we need JSONObject. Anyone could please guide to how to do this
You can do bulk insertion by passing hashmap of your json objects to Elasticsearch Bulk API.
You can create Hashmap by parsing your JSON file through JSONParser.
Here is the code for the same :
Code :
Integer id= 1;
//You need to call this method for inserting bulk documents which
// internally calls `createBulkRequest` and `parseObjectList` methods.
//This method uses JSONParser to parse your file and convert into JSONArray.
public String insertBulkDocuments() throws Exception {
Object obj = new JSONParser().parse(new FileReader(<path-of-file>));
JSONArray objList= (JSONArray) obj;
BulkRequest request = createBulkRequest(objList);
BulkResponse bulkresp=restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
return bulkresp.status().toString();
}
// Each JSONArray element that was obtained through first method
//is parsed individually through Gson and converted into you defined Object.
//This object is then converted to Map and passed to IndexRequest object.
private BulkRequest createBulkRequest(JSONArray objList) {
BulkRequest request = new BulkRequest();
objList.forEach( obj -> parseObjectList((JSONObject) obj, request,id++));
return request;
}
private void parseObjectList(JSONObject obj, BulkRequest request, int id) {
Gson gson = new GsonBuilder().create();
NutrientDocument doc = gson.fromJson(obj.toJSONString(), NutrientDocument .class);
Map<String, Object> documentMapper = objectMapper.convertValue(doc, Map.class);
IndexRequest indexRequest = new IndexRequest(<your-index-name>).id(Integer.toString(id)).source(documentMapper);
request.add(indexRequest);
}
You need to create Custom object which has same feilds as your json . I have created NutrientDocument for testing which has same fields as your JSON and this I am using in parseObjectList method.
public class NutrientDocument {
private String Nutrient;
private Float Amount;
private String Unit;
public String getNutrient() {
return Nutrient;
}
public void setNutrient(String nutrient) {
Nutrient = nutrient;
}
public Float getAmount() {
return Amount;
}
public void setAmount(Float amount) {
Amount = amount;
}
public String getUnit() {
return Unit;
}
public void setUnit(String unit) {
Unit = unit;
}
}
NOTE :
For each document elasticserach generates unique id .
For creating our own id value instead of Elasticsearch autogenerated value, we are using id variable. But, if you want to go with Elasticsearch autogenerated number , you can create IndexRequest object as below in parseObjectList method and remove id variable wherever we are passing.
IndexRequest indexRequest = new IndexRequest().source(documentMapper);
I'm using this method that returns a Set<String> but in fact what I got is a Json string like this
[
{
"id":"Id1"
},
{
"id":"Id2",
"title":"anyTitle"
}
]
My goal is to get the value of key "id". I've also made a java bean to map the data:
public class Data {
private String id;
private String title;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
I tryied to parse using gson but all I can get is an error: Cannot cast 'java.util.LinkedHashMap$LinkedKeyIterator' to 'com.google.gson.stream.JsonReader'
So, obviously I'm doing something wrong:
Set<String> availableData = getData(); //this method returns a json string
Iterator<String> itr = availableData.iterator();
while (itr.hasNext()) {
Gson gson = new Gson();
JsonParser parser = new JsonParser();
JsonObject object = (JsonObject) parser.parse(itr.next());
Data data = gson.fromJson(object, Data.class);
}
update: The actual error is: Type mismatch Can't assign com.google.common.collect.Maps$TransformedEntriesMap to java.lang.String
In that line you pass an iterator:
JsonObject object = (JsonObject) parser.parse((JsonReader) itr);
But you should pass a next element:
JsonObject object = (JsonObject) parser.parse(itr.next());
In addition you got an extra comma in you JSON.
You can replace the whole block with that line:
Data data = gson.fromJson(itr.next(),Data.class)
Use Jackson mapper. You can directly convert it into an object and retrieve through getters.
ObjectMapper objectMapper = new ObjectMapper();
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
try {
Car car = objectMapper.readValue(carJson, Car.class);
System.out.println("car brand = " + car.getBrand());
System.out.println("car doors = " + car.getDoors());
} catch (IOException e) {
e.printStackTrace();
}
So, following this related issue: https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/5154, finally I map this using JSONArray and streams from java8
Set<String> availableData = getData();
JSONArray dataArray = new JSONArray(availableData);
List<Object> dataList = dataArray.toList();
Object o = dataList.stream()
.filter(c -> ((Map) c).get("id").toString().contains("Id1"))
.findFirst().orElse(null);
return ((Map)o).get("id").toString();
Maybe you want to known how to use Gson to unserialized json to java object.
Here are two ways I can give you.
public void parse() {
String jsonString = "[\n" +
" {\n" +
" \"id\":\"Id1\"\n" +
" },\n" +
" {\n" +
" \"id\":\"Id2\",\n" +
" \"title\":\"anyTitle\"\n" +
" }\n" +
"]";
Gson gson = new Gson();
// Use Gson Type
Type setType = new TypeToken<HashSet<Data>>(){}.getType();
Set<Data> dataSet = gson.fromJson(jsonString, setType);
// Print [Data{id='Id2', title='anyTitle'}, Data{id='Id1', title='null'}]
System.out.println(dataSet);
// Use Java Array
Data[] dataArray = gson.fromJson(jsonString, Data[].class);
// Print [Data{id='Id1', title='null'}, Data{id='Id2', title='anyTitle'}]
System.out.println(Arrays.toString(dataArray));
}
I want to read json file as follow;
{
"M": {
"row": [
{
"col1": "c00"
},
{
"col1": "c10",
"col2": "c11"
},
{
"col1": "c20",
"col2": "c21",
"col3": "c22"
}
]
}
}
Next to reading, I want to print "c00","c10","c11","c20","c21","c22" but without giving element as "col1","col2","col3"...
Thanks for helping.
You can use org.json library for this. It is here. General idea:
JSONObject obj = new JSONObject(sourceString);
for(String key : obj.keys()){
String value = obj.getString(key);
// Process value here
}
Use any JSON parsing library such as GSON or Jackson and convert it into Java Object.
Sample code using GSON library
Type type = new TypeToken<Map<String, Object>>() {}.getType();
Map<String, Object> data = new Gson().fromJson(jsonString, type);
System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(data));
// get the desired value from map
Map<String,ArrayList<Map<String,String>>> mMap=(Map<String,ArrayList<Map<String,String>>>)data.get("M");
ArrayList<Map<String,String>> rowArray=mMap.get("row");
for(Map<String,String> colMap:rowArray){
for(String value:colMap.values()){
System.out.println(value);
}
}
You can convert JSON string into Java POJO class as well that is replica of the JSON string
class MDetails {
private MDetail M;
// getter & setter
}
class MDetail {
private ArrayList<Map<String, String>> row;
// getter & setter
}
...
MDetails data = new Gson().fromJson(jsonString, MDetails.class);
for (Map<String, String> colMap : data.getM().getRow()) {
for (String value : colMap.values()) {
System.out.println(value);
}
}
You can use different field name using #SerializedName annotation.
class MDetails {
#SerializedName("M")
private MDetail mDetail;
// getter & setter
}
As per comments, the keys are dynamic so iterate the map containing another map in it and print all the values whose key starts with col
sample code: (call below method that recursively iterate all keys and values)
public static void printColValues(Object data) {
if (data instanceof Map) {
for (Map.Entry<String, Object> entry : ((Map<String, Object>) data).entrySet()) {
String key = entry.getKey();
if (key.startsWith("col")) {
System.out.println(entry.getValue());
} else {
printColValues(entry.getValue());
}
}
} else if (data instanceof List) {
for (Object obj : (List) data) {
printColValues(obj);
}
}
}
output:
c00
c10
c11
c20
c21
c22
OR if nothing works then try with regex pattern but keep it as last resort
("col\d+":)("[^"]*")
Here is online demo
Or try with Reluctant Qualifier
("col\d+":)(".*?")
Here is demo
sample code:
String jsonString = "{\"M\":{\"row\":[{\"col1\":\"c00\"},{\"col1\":\"c10\",\"col2\":\"c11\"},{\"col1\":\"c20\",\"col2\":\"c21\",\"col3\":\"c22\"}]}}";
Pattern p = Pattern.compile("(\"col\\d+\":)(\"[^\"]*\")");
Matcher m = p.matcher(jsonString);
while (m.find()) {
System.out.println(m.group(2));
}
output:
"c00"
"c10"
"c11"
"c20"
"c21"
"c22"
Code updated to print all values regardless of keys
public static void printColValues(Object data) {
if (data instanceof Map) {
for (Map.Entry<String, Object> entry : ((Map<String, Object>) data).entrySet()) {
Object value=entry.getValue();
if (value instanceof String) {
System.out.println(value);
} else {
printColValues(value);
}
}
} else if (data instanceof List) {
for (Object obj : (List) data) {
printColValues(obj);
}
}
}
I have this class
public static class SomeClass {
public SomeClass(String field) {
this.field = field;
}
private final String field;
public String getField() {
return field;
}
}
I have also this test (edited)
#Test
public void testStringifyMapOfObjects() {
Map<String, SomeClass> original = Maps.newTreeMap();
original.put("first", new SomeClass("a"));
original.put("second", new SomeClass("b"));
String encoded = JsonUtil.toJson(original);
Map<String, SomeClass> actual = JsonUtil.fromJson(encoded, Map.class);
Assert.assertEquals("{'first':{'field':'a'},'second':{'field':'b'}}", encoded.replaceAll("\\s", "").replaceAll("\"", "'"));
Assert.assertEquals(original.get("first"), actual.get("first"));
}
The test fails with
junit.framework.AssertionFailedError: expected:<eu.ec.dgempl.eessi.facade.transport.test.TestToolTest$SomeClass#6e3ed98c> but was:<{field=a}>
at junit.framework.Assert.fail(Assert.java:47)
at junit.framework.Assert.failNotEquals(Assert.java:277)
at junit.framework.Assert.assertEquals(Assert.java:64)
at junit.framework.Assert.assertEquals(Assert.java:71)
at eu.ec.dgempl.eessi.facade.transport.test.TestToolTest.testStringifyMapOfObjects(TestToolTest.java:90)
Can I make json to properly serialize objects as the values of the map or should I use something else?
edited
public class JsonUtil {
private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(JsonUtil.class);
public static <T> String toJson(T data) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.INDENT_OUTPUT, true);
try {
return mapper.writeValueAsString(data);
} catch (IOException e) {
LOG.warn("can't format a json object from [" + data + "]", e);
return null;
}
//
// return Json.stringify(Json.toJson(data));
}
public static <T> T fromJson(String description, Class<T> theClass) {
try {
JsonNode parse = new ObjectMapper().readValue(description, JsonNode.class);
T fromJson = new ObjectMapper().treeToValue(parse, theClass);
return fromJson;
} catch (JsonParseException e) {
// throw new RuntimeException("can't parse a json object of type " + theClass.getName() + " from [" + description + "]", e);
LOG.warn("can't parse a json object from [" + description + "]", e);
return null;
} catch (JsonMappingException e) {
// throw new RuntimeException("can't parse a json object of type " + theClass.getName() + " from [" + description + "]", e);
LOG.warn("can't parse a json object from [" + description + "]", e);
return null;
} catch (IOException e) {
// throw new RuntimeException("can't parse a json object of type " + theClass.getName() + " from [" + description + "]", e);
LOG.warn("can't parse a json object from [" + description + "]", e);
return null;
}
}
}
You are running into a problem related to Java generics. To summarize, when deserializing data into a non-reifiable type (aka a type for which actual type information is not available at runtime) you need to use a supertype token. You can get more detail about what a supertype token is (and why you need to use one) by reading these SO posts:
Pass parameterized type to method as argument
Error using Jackson and JSON
Deserialize JSON to ArrayList using Jackson
And also from the Jackson documentation:
Data Binding With Generics
TypeReference Javadoc
The basic problem is that when you use a typical generic object, the actual type parameters for the object aren't available at runtime. Therefore Jackson doesn't know which actual class to instantiate and deserialize your data into.
The easiest way to get around the problem would be adding an overload to your JSON utility class, that accepts a type reference (as opposed to a Class<T>). For example:
public static <T> T fromJson(String json, TypeReference<T> typeRef) {
if(json == null || typeRef == null) return null;
return new ObjectMapper().readValue(json, typeRef);
}
To be used as such:
Map<String, SomeClass> actual = JsonUtil.fromJson(
encoded,
new TypeReference<Map<String, SomeClass>>(){});
I discovered that the simplest solution is to create a "container" class that will contain the map. This is working probably because the container has enough type details for the map, as opposed to the case when a map is used directly.
public static class SomeClass {
private final String field;
private SomeClass() {
this("wrong");
}
public SomeClass(String field) {
this.field = field;
}
public String getField() {
return field;
}
#Override
public String toString() {
return "SomeClass[" + field + "]";
}
}
public static class SomeClassContainer {
private final Map<String, SomeClass> all = Maps.newTreeMap();
public Map<String, SomeClass> getAll() {
return all;
}
}
After this ... the updated test is
#Test
public void testStringifyMapOfObjects() {
SomeClassContainer original = new SomeClassContainer();
original.getAll().put("first", new SomeClass("a"));
original.getAll().put("second", new SomeClass("b"));
String encoded = JsonUtil.toJson(original);
System.out.println(encoded);
SomeClassContainer actual = JsonUtil.fromJson(encoded, SomeClassContainer.class);
System.out.println(ObjectUtils.toString(actual));
Assert.assertEquals("{'all':{'first':{'field':'a'},'second':{'field':'b'}}}", encoded.replaceAll("\\s", "").replaceAll("[\"]", "'"));
Assert.assertEquals("class eu.ec.dgempl.eessi.facade.transport.test.TestToolTest$SomeClass", actual.getAll().get("first").getClass().toString());
Assert.assertEquals(original.getAll().get("first").toString(), actual.getAll().get("first").toString());
Assert.assertEquals(original.getAll().get("second").toString(), actual.getAll().get("second").toString());
}
I'm using java.util.resourcebundle to format my JSTL messages and this works fine:
I use the class MessageFormat you can see here. Now I want to encapsulate this to a method that is just getParametrizedMessage(String key, String[]parameters) but I'm not sure how to do it. Now there is quite a lot of work to display just one or two messages with parameters:
UserMessage um = null;
ResourceBundle messages = ResourceBundle.getBundle("messages");
String str = messages.getString("PF1");
Object[] messageArguments = new String[]{nyreg.getNummer()};
MessageFormat formatter = new MessageFormat("");
formatter.applyPattern(messages.getString("PI14"));
String outputPI14 = formatter.format(messageArguments);
formatter.applyPattern(messages.getString("PI15"));
String outputPI15 = formatter.format(messageArguments)
if(ipeaSisFlag)
if(checkIfPCTExistInDB && nyreg.isExistInDB()) {
//um = new ExtendedUserMessage(MessageHandler.getParameterizedMessage("PI15", new String[]{nyreg.getNummer()}) , UserMessage.TYPE_INFORMATION, "Info");
um = new ExtendedUserMessage(outputPI15 , UserMessage.TYPE_INFORMATION, "Info");
…and so on. Now can I move this logic to a static class MessageHandler.getParameterizedMessage that now is not working and looking like this:
private final static String dictionaryFileName="messages.properties";
public static String getParameterizedMessage(String key, String [] params){
if (dictionary==null){
loadDictionary();
}
return getParameterizedMessage(dictionary,key,params);
}
private static void loadDictionary(){
String fileName = dictionaryFileName;
try {
dictionary=new Properties();
InputStream fileInput = MessageHandler.class.getClassLoader().getResourceAsStream(fileName);
dictionary.load(fileInput);
fileInput.close();
}
catch(Exception e) {
System.err.println("Exception reading propertiesfile in init "+e);
e.printStackTrace();
dictionary=null;
}
}
How can I make using my parametrized messages as easy as calling a method with key and parameter?
Thanks for any help
Update
The logic comes from an inherited method that in in the abstract class that this extends. The method looks like:
protected static String getParameterizedMessage(Properties dictionary,String key,String []params){
if (dictionary==null){
return "ERROR";
}
String msg = dictionary.getProperty(key);
if (msg==null){
return "?!Meddelande " +key + " saknas!?";
}
if (params==null){
return msg;
}
StringBuffer buff = new StringBuffer(msg);
for (int i=0;i<params.length;i++){
String placeHolder = "<<"+(i+1)+">>";
if (buff.indexOf(placeHolder)!=-1){
replace(buff,placeHolder,params[i]);
}
else {
remove(buff,placeHolder);
}
}
return buff.toString();
}
I think I must rewrite the above method in order to make it work like a resourcebundle rather than just a dictionary.
Update 2
The code that seems to work is here
public static String getParameterizedMessage(String key, Object [] params){
ResourceBundle messages = ResourceBundle.getBundle("messages");
MessageFormat formatter = new MessageFormat("");
formatter.applyPattern(messages.getString(key));
return formatter.format(params);
}
I'm not really sure what you're trying to achive, here's what I did in the past:
public static final String localize(final Locale locale, final String key, final Object... param) {
final String name = "message";
final ResourceBundle rb;
/* Resource bundles are cached internally,
never saw a need to implement another caching level
*/
try {
rb = ResourceBundle.getBundle(name, locale, Thread.currentThread()
.getContextClassLoader());
} catch (MissingResourceException e) {
throw new RuntimeException("Bundle not found:" + name);
}
String keyValue = null;
try {
keyValue = rb.getString(key);
} catch (MissingResourceException e) {
// LOG.severe("Key not found: " + key);
keyValue = "???" + key + "???";
}
/* Message formating is expensive, try to avoid it */
if (param != null && param.length > 0) {
return MessageFormat.format(keyValue, param);
} else {
return keyValue;
}
}