How can i deseralize a dynamic generated JSON Object? - java

Im having trouble to deserialize the object back into the original class. The problem is that the Component class have to be serialized in that spezific way with the features beeing a seperate list/set object.The attributes have to be seperate key/value pairs. If i try to deserialize the JSON/object back, i get an "canĀ“t construct instance of LinkedHashMap" error.
Here is the class to parse to JSON
public class Component {
private Set<Attribute> attributes;
private Set<Feature> features;
}
public class Attribute {
private String type;
private String key;
private String value;
}
public class Feature {
private String key;
private String value;
}
Here is the serializer
public class ComponentSerializer extends StdSerializer<Component> {
public ComponentSerializer(){
this(null);
}
public ComponentSerializer(Class<Component> c){
super(c);
}
#Override
public void serialize(Component com, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
for(Attribute att : com.getAttributes()){
gen.writeStringField(att.getKey(),att.getValue());
}
Set<Feature> features = new HashSet<Feature>();
for(Feature ftr : com.getFeatures()){
features.add(new Feature(ftr.getKey(), ftr.getValue()));
}
gen.writeObjectField("features", features);
gen.writeEndObject();
}
}
The outcome JSON is something like
{
"Foo": "Fooo",
"Bar": "Barr",
...
"features": [
{
"key": "foo",
"value": "bar"
},
{
"key": "bar",
"value": "foor"
}
...
]
}
My attempt on deserialization
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(p.getCodec().readTree(p).toString(), new TypeReference<Map<String, Object>>() {
});
Set<Attribute> attributes = new HashSet<>();
Set<Feature> features = new HashSet<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("features")){
//Here i cant create the Set<Features> from the object without the exception
continue;
}
attributes.add(new Attribute("", entry.getKey(), entry.getValue().toString()));
}

ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(p.getCodec().readTree(p).toString(), new TypeReference<Map<String, Object>>() {});
Set<Attribute> attributes = new HashSet<>();
Set<Feature> features = new HashSet<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("features")) {
List<LinkedHashMap> featureList = (List<LinkedHashMap>) entry.getValue();
featureList.stream().forEach(feature -> {
features.add(new Feature(feature.get("key").toString(), feature.get("value").toString()));
});
continue;
}
attributes.add(new Attribute("", entry.getKey(), entry.getValue().toString()));
}

Related

Thread safety of static ArrayList in 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?

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

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 map a POJO to a custom Map using jackson ObjectMapper?

I have a custom map implementation called ObjectMap as follows:
public class ObjectMap extends LinkedHashMap<String, Object> implements Serializable {
...
}
I can convert any POJO to this ObjectMap using jackson ObjectMapper as follows:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(
objectMapper.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE).
withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
);
ObjectMap objectMap = objectMapper.convertValue(object, new TypeReference<ObjectMap>() {});
But the problem is if my POJO's complex fields are being mapped to a LinkedHashMap not ObjectMap. So how do I enforce ObjectMapper to map internal fields also into a ObjectMap instead of LinkedHashMap?
I have come up with below solution at last:
public class Document extends LinkedHashMap<String, Object> implements Serializable {
}
public class JacksonMapper {
private ObjectMapper objectMapper;
public <T> Document asDocument(T object) {
ObjectMapper objectMapper = getObjectMapper();
JsonNode node = objectMapper.convertValue(object, JsonNode.class);
return loadDocument(node);
}
protected ObjectMapper getObjectMapper() {
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.setVisibility(
objectMapper.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE).
withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
}
return objectMapper;
}
private Document loadDocument(JsonNode node) {
Document document = new Document();
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
String name = entry.getKey();
JsonNode value = entry.getValue();
document.put(name, loadObject(value));
}
return document;
}
private Object loadObject(JsonNode node) {
if (node == null) return null;
try {
switch (node.getNodeType()) {
case ARRAY:
return loadArray(node);
case BINARY:
return node.binaryValue();
case BOOLEAN:
return node.booleanValue();
case MISSING:
case NULL:
return null;
case NUMBER:
return node.numberValue();
case OBJECT:
return loadDocument(node);
case POJO:
return loadDocument(node);
case STRING:
return node.textValue();
}
} catch (IOException e) {
return null;
}
return null;
}
private List loadArray(JsonNode array) {
if (array.isArray()) {
List list = new ArrayList();
Iterator iterator = array.elements();
while (iterator.hasNext()) {
Object element = iterator.next();
if (element instanceof JsonNode) {
list.add(loadObject((JsonNode) element));
} else {
list.add(element);
}
}
return list;
}
return null;
}
}
Hope this will help someone someday.

Categories