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
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?
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
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;
}
}
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
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.