Thread safety of static ArrayList in Java - java

I have a nested json like below. From this I want to get specific values which are not again json objects. e.g. values against keys "first_name" or "last_name" or "purpose" etc
[
{
"purpose":"Audit",
"sender_name":"Tester One",
"sent_date":"10-10-2020",
"approval":true,
"agency":{
"name":"Test Agency",
"id":1234
},
"records":[
{
"students":{
"first_name":"FirstOne",
"last_name":"LastOne",
"address":{
"street":"123 Street",
"city":"Test City",
"zip":12345
}
},
"employees":{
"first_name":"EmpFirst",
"last_name":"EmpLast",
"address":{
"street":"ABC Street",
"city":"ABC City",
"zip":99921
}
}
}
],
"completion":true
}
]
For this I wrote 2 recursive methods and another method which will invoke this method. Following is my code.
public class JsonUtils {
private static ArrayList<Object> resultSet = new ArrayList<Object>();
/*Method to convert json to Map*/
public static Map<String, Object> convertJsonArrayToMap(String filePath) {
List<Object> list = null;
ObjectMapper mapper = new ObjectMapper();
try {
list = mapper.readValue(new File(filePath), new TypeReference<List<Object>>() {
});
} catch (IOException e) {
e.printStackTrace();
}
Map<String, Object> data = (Map<String, Object>) list.get(0);
return data;
}
/*Method to iterate nested HashMap*/
public static Object jsonMapIterator(Map<String, Object> map, String key) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
jsonMapIterator((Map<String, Object>) entry.getValue(), key);
} else if (entry.getValue() instanceof ArrayList) {
jsonListIterator((ArrayList<Object>) entry.getValue(), key);
} else {
if (entry.getKey() == key) resultSet.add(entry.getValue());
}
}
return resultSet!=null?resultSet:null;
}
/*Method to iterate array list of objects*/
public static Object jsonListIterator(ArrayList<Object> list, String key) {
AtomicReference<Object> value = null;
Consumer<Object> action = i -> {
if (i instanceof ArrayList) {
jsonListIterator((ArrayList<Object>) i, key);
} else if (i instanceof Map) {
jsonMapIterator((Map<String, Object>) i, key);
} else {
value.set(i);
}
};
list.stream().forEach(action);
return value;
}
/*method to invoke recursive search and return all values for any given key*/
public static Object jsonValueFetcher(Map<String, Object> jsonData, String key){
resultSet.clear();
ArrayList<Object> values;
values = (ArrayList<Object>) jsonMapIterator(jsonData, key);
return values.size()==0?null:values;
}
public static void main(String[] args) {
Map<String, Object> jsonData = convertJsonArrayToMap("src/test/resources/nestedJson.json");
System.out.println(jsonValueFetcher(jsonData,"first_name"));
}
}
Now in here, how can I make the static variable resultSet and the two recursive methods thread safe in case of a parallel test execution?

Related

Return response from API as inner JSON objects

I use this code to get a list of countries as full name and ISO code:
public Map<String, Object> getCountryNameCodeList() {
String[] countryCodes = Locale.getISOCountries();
Map<String, Object> list = new HashMap<>();
for (String countryCode : countryCodes) {
Locale obj = new Locale("", countryCode);
list.put(obj.getDisplayCountry().toString(), obj.getCountry());
}
return list;
}
Rest API:
#GetMapping("shipping_countries")
public ResponseEntity<Map<String, Object>> getShippingCountries() {
return new ResponseEntity<Map<String, Object>>(countriesService.getCountryNameCodeList(), HttpStatus.OK);
}
I get the response data in this format:
{
"Papua New Guinea": "PG",
"Cambodia": "KH",
"Kazakhstan": "KZ",
"Paraguay": "PY",
.....
}
I would like to get the data this way:
[
{
name: "Papua New Guinea",
value: "PG"
},
{
name: "Unites States",
value: "US"
},
....
]
How I can modify the Java code to return the data this way?
Try this approach. You need to use data transfer object to return customized data.
Create a class DTO.
public class DTO {
private String key;
private String value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String toString() {
return "DTO [key=" + key + ", value=" + value + "]";
}
}
Create Rest API in the controller. Example :
#RestController
public class Sample {
#RequestMapping("shipping_countries")
public ResponseEntity<List<DTO>> getShippingCountries() {
Map<String, String> map = new HashMap<>();
map.put("Papua New Guinea", "PG");
map.put("Cambodia", "KH");
List<DTO> list = getCustomisedData(map);
return ResponseEntity.ok(list);
}
private List<DTO> getCustomisedData(Map<String, String> map) {
List<DTO> dtos = new ArrayList();
for(Entry<String, String> value: map.entrySet()) {
DTO dto = new DTO();
dto.setKey(value.getKey());
dto.setValue(value.getValue());
dtos.add(dto);
}
return dtos;
}
}
Output :
The response you are getting is the JSON representation of a map, which is what you return.
The json you want is an array of objects, so if you want to return that- the easiest way will be to return it like that, is to return the set of Map.Entry from your map. Something like that:
#GetMapping("shipping_countries")
public ResponseEntity<Set<Map.Entry<String, Object>>> getShippingCountries() {
return new ResponseEntity<>(countriesService.getCountryNameCodeList().entrySet(), HttpStatus.OK);
}
Other way can be to create a Json serializer for the response, but it seems like an overkill

Split String based on delimiter and convert to n level Pair of Pair based on count of delimiter

There is a hashmap and below is the requirement :
Map<String, Object> objectmetainfo = new HashMap();
objectmetainfo.put("userdetails.info.metadata.user.home.address.details", "address");
objectmetainfo.put("userdetails.info.metadata.user.id", "id");
objectmetainfo.put("userdetails.info.metadata.userSupervisor.id", "id");
objectmetainfo.put("info.metadata.code", "code");
objectmetainfo.put("zip", "zip");
Get all the records of hashmap and iterate it
Split the Key based on delimiter and convert it to Pair or Hashmap
The number of delimiter will vary in each string
Below should be the output :
E.g.: For "userdetails.info.metadata.user.home.address.details", "address", below output is required
HashMap<userdetails, HashMap<info, HashMap<metadata, HashMap<user, HashMap<home, HashMap<address, Map<details, address>>>>>>>
or Pair<String, Object> pair = new Pair("userdetails", new Pair("info", new Pair("metadata", new Pair("user", new Pair("home", new Pair("address", new Pair("details", "addressvalue")))))));
Assuming your string won't be crazy long this would work, otherwise you'd get a StackOverflow error.l
I did this using a recursive approach
Split keys by "."
Convert them to the list iterator
Iterate this list recursively to create a nested map
At the end of recursion put the value from objectmetainfo map.
Create an empty result map and recursively merge all the results.
Code:
import java.util.*;
public Map<String, Object> nestedMaps(Iterator<String> keys, String value) {
if (keys.hasNext()) {
String key = keys.next();
Map<String, Object> nestMap = nestedMaps(keys, value);
Map<String, Object> map = new HashMap<>();
map.put(key, nestMap);
if (Objects.equals(nestMap, null))
map.put(key, value);
return map;
}
return null;
}
public void mergeNested(Object srcObj, Object targetObj) {
if (srcObj instanceof Map && targetObj instanceof Map) {
Map<String, Object> srcMap = (Map<String, Object>) srcObj;
Map<String, Object> targetMap = (Map<String, Object>) targetObj;
for (String targetKey : targetMap.keySet()) {
if (srcMap.containsKey(targetKey)) {
mergeNested(srcMap.get(targetKey), targetMap.get(targetKey));
} else {
srcMap.putAll(targetMap);
}
}
}
}
public Map<String, Object> objectmetainfo = new LinkedHashMap<>();
objectmetainfo.put("userdetails.info.metadata.user.home.address.details", "addressValue");
objectmetainfo.put("userdetails.info.metadata.user.id", "id");
objectmetainfo.put("userdetails.info.metadata.userSupervisor.id", "id");
objectmetainfo.put("info.metadata.code", "code");
objectmetainfo.put("zip", "zip");
public Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, Object> e : objectmetainfo.entrySet()) {
List<String> keys = new ArrayList<>(Arrays.asList(e.getKey().split("\\.")));
Map<String, Object> nestedMaps = nestedMaps(keys.iterator(), String.valueOf(e.getValue()));
mergeNested(result, nestedMaps);
}
System.out.println(result);
I printed out all the hashmaps using toString method.
Output:
{
zip= zip,
userdetails= {
info= {
metadata= {
userSupervisor= {
id= id
},
user= {
id= id,
home= {
address= {
details= addressValue
}
}
}
}
}
},
info= {
metadata= {
code= code
}
}
}
Below is the modified logic to remove 2nd recursive call, with this method we are passing the objMap as reference and finally we will have the objMap ready with result
Map<String, Object> objMap = new HashMap<>();
for (Map.Entry<String, Object> e : getObjectMetaInfoMap().entrySet()) {
populateMetaDataMap(keys.iterator(), String.valueOf(e.getValue()), objMap, true, StringUtils.EMPTY);
}
public static Map<String, Object> populateMetaDataMap(Iterator<String> keys, String value, Map<String, Object> objMap, boolean newCall, String matchingKey) {
if (keys.hasNext()) {
String key = keys.next();
if(objMap.get(key) != null && objMap.get(key) instanceof Map) {
return populateMetaDataMap(keys, value, (Map<String, Object>) objMap.get(key), newCall, matchingKey );
} else {
if(newCall) {
newCall = false;
matchingKey = key;
}
Map<String, Object> nestMap = populateMetaDataMap(keys, value, objMap, newCall, matchingKey);
Map<String, Object> map = new HashMap<>();
if (Objects.equals(nestMap, null))
map.put(key, value);
else
map.put(key, nestMap);
if(key.equals(matchingKey)) {
if(Objects.equals(nestMap, null)) {
objMap.put(key, value);
} else {
objMap.put(key, nestMap);
}
}
return map;
}
}
return null;
}

Jackson: how to convert flat json into nested json

How can I convert a json string like
{
"a.b": 1,
"a.c.d": 2
}
into json string
{
"a": {
"b": 1,
"c": {
"d": 2
}
}
}
by using ObjectMapper?
The easiest option is to create custom deserializer or custom been with #JsonAnySetter and #JsonAnyGetter Here is example:
public static final String json = "{\"a.b\": 1,\"a.c.d\": 2}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
HelperBean bean = mapper.readValue(json, HelperBean.class);
System.out.println(mapper.writeValueAsString(bean));
// result: {"a":{"b":1,"c":{"d":2}}}
}
public static class HelperBean{
#JsonIgnore
private Map<String, Object> data = new TreeMap<>();
#JsonAnySetter
public void setDays(String key, Object value){
String[] parts = key.split("\\.");
Map<String, Object> currMap = data;
for (int i = 0; i< parts.length; i++){
String part = parts[i];
Object subMap = currMap.get(part);
if (i == parts.length - 1) // last node
currMap.put(part, value);
else if(subMap == null){ // new node
subMap = new TreeMap<>();
currMap.put(part, subMap);
currMap = (Map<String, Object>) subMap;
}else if (subMap instanceof Map){ // existing node
currMap.put(part, subMap);
currMap = (Map<String, Object>) subMap;
} else { // node conflict
// handle exception when a.b = 1 and a.b.c = 1
}
}
}
#JsonAnyGetter
public Map<String, Object> getData() {
return data;
}
}

Jackson map deserialization - value replacement

What's the best way to deserialize this Json object?
{
"key1" : "val1",
"key2" : "blank"
}
into a java hashmap, where the string blank is replaced with null?
{
"key1" : "val1",
"key2" : null
}
I am currently using Jackson for deserialization.
You will find part of the answer here.
You just need to manipulate the line inside the while loop:
Object value;
if (object.get(key).equals("blank")) {
value = "null";
} else {
value = object.get(key);
}
and make print out will give:
System.out.println(map.get("key1")); // returns val1
System.out.println(map.get("key2")); // returns null
You final code will look like this, and you might need to import the proper .jar files:
import com.orsoncharts.util.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import static com.sun.xml.internal.ws.binding.WebServiceFeatureList.toList;
public class JsonAnswerOne {
public static void main(String[] args) throws JSONException {
String input = "{\n" +
" \"key1\" : \"val1\",\n" +
" \"key2\" : \"blank\"\n" +
"}";
parse(input);
}
private static void parse(String input) throws JSONException {
JSONObject mainObject = new JSONObject(input);
Map<String, Object> map = jsonToMap(mainObject);
System.out.println(map.get("key1")); // returns val1
System.out.println(map.get("key2")); // returns null
}
private static Map<String, Object> jsonToMap(JSONObject json) throws JSONException {
Map<String, Object> retMap = new HashMap<String, Object>();
if (json != JSONObject.NULL) {
retMap = toMap(json);
}
return retMap;
}
private static Map<String, Object> toMap(JSONObject object) throws JSONException {
Map<String, Object> map = new HashMap<String, Object>();
Iterator<String> keysItr = object.keys();
while (keysItr.hasNext()) {
String key = keysItr.next();
Object value;
if (object.get(key).equals("blank")) {
value = "null";
} else {
value = object.get(key);
}
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
} else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
map.put(key, value);
}
return map;
}
}
I tried this and ended up with this:
// use of the deserializer
String json = "{\"key1\":\"val1\",\"key2\":\"blank\"}";
ObjectMapper mapperMap = new ObjectMapper();
SimpleModule moduleMap = new SimpleModule();
moduleMap.addDeserializer(Map.class, new MapDeserializer());
mapperMap.registerModule(moduleMap);
Map map = mapperMap.readValue(json, Map.class);
// custom deserializer
public class MapDeserializer extends StdDeserializer<Map<String, String>> {
public MapDeserializer() {
this(null);
}
public MapDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Map<String, String> deserialize(JsonParser jp, DeserializationContext context)
throws IOException {
// definitely not the best way but it works...
Map<String, String> map = new HashMap<>();
String[] keys = new String[] {"key1", "key2"};
JsonNode node = jp.getCodec().readTree(jp);
String value;
for (String key : keys) {
value = node.get(key).asText();
if (value.equals("blank")) {
value = null;
}
map.put(key, value);
}
return map;
}
}
Full example solution with an additional example to deserialize the JSON into another class:
https://gist.github.com/audacus/e70ce0f3cd4b17197d911769e05b237e

How to Read Json File Without Giving Element Names in Java

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);
}
}
}

Categories