I'm developing a RESTful service using Java and Spring-Boot and ran into a problem. I'm getting a put request with request body like:
{
"key":{
"par1":"val1",
"par2":"val2"
},
"data":{
"par1":"val1",
"par2":"val"
}
}
For parsing it I need to create own #RequestBody type. This is it:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.JSONWrappedObject;
import java.io.Serializable;
import java.util.Map;
public class UpdateInfo {
private Map<String, Object> mapKey;
private Map<String, Object> mapData;
ObjectMapper mapper;
public void setMapKey(JsonNode key) {
this.mapKey = mapper.convertValue(key, new TypeReference<Map<String, Object>>(){});
}
public void setMapData(JsonNode) {
this.mapData = mapper.convertValue(data, new TypeReference<Map<String, Object>>(){});
}
public Map<String, Object> getKeys() {
return mapKey;
}
public Map<String, Object> getData() {
return mapData;
}
}
Logically it must work, but I am getting an error:
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: null; nested exception is com.fasterxml.jackson.databind.JsonMappingException: N/A
at [Source: (PushbackInputStream); line: 6, column: 3] (through reference chain: com.sas.rus.spm.UpdateInfo["key"])]
Really can't get the reason, hope for your help
Simply try:
public class UpdateInfo {
private Map<String, Object> key;
private Map<String, Object> data;
//getter and setter
public Map<String, Object> getKey(){
return key;
}
public void setKey(Map<String,Object> key){
this.key = key
}
public Map<String, Object> getData(){
return data;
}
public void setData(Map<String,Object> data){
this.data = data
}
}
JSON parse error: null it is just because you din't initialize mapKey and mapData and getter method is returning null.
Also change the getKeys() method to getKey() as in json your json key is key.
Your class will look like
public class UpdateInfo {
private Map<String, Object> mapKey = new HashMap<>();
private Map<String, Object> mapData = new HashMap<>();
ObjectMapper mapper;
public void setMapKey(JsonNode key) {
this.mapKey = mapper.convertValue(key, new TypeReference<Map<String, Object>>() {
});
}
public void setMapData(JsonNode data) {
this.mapData = mapper.convertValue(data, new TypeReference<Map<String, Object>>() {
});
}
public Map<String, Object> getKey() {
return mapKey;
}
public Map<String, Object> getData() {
return mapData;
}
}
Related
I'm coding an Spring-boot service and I'm using jackson ObjectMapper in order to handle with my jsons.
I need to split a json like this:
{
"copy": {
"mode": "mode",
"version": "version"
},
"known": "string value",
"unknown": {
"field1": "sdf",
"field2": "sdfdf"
},
"unknown2": "sdfdf"
}
I mean, my bean is like this:
public class MyBean {
private CopyMetadata copy;
private String known;
private Object others;
}
I'd like to populate known fields to MyBean properties, and move the other unknown properties inside MyBean.others property.
Known properties are which are placed as a field inside MyBean.
Any ideas?
A possible solution to this problem is to use the jackson annotations #JsonAnyGetter and #JsonAnySetter
Your Model Mybean.class should look something like this and it should work
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
public class MyBean {
private CopyMetadata copy;
private String known;
private Map<String, Object> others = new HashMap<>();
public CopyMetadata getCopy() {
return copy;
}
public void setCopy(CopyMetadata copy) {
this.copy = copy;
}
public String getKnown() {
return known;
}
public void setKnown(String known) {
this.known = known;
}
public Map<String, Object> getOthers() {
return others;
}
public void setOthers(Map<String, Object> others) {
this.others = others;
}
#JsonAnyGetter
public Map<String, Object> getUnknownFields() {
return others;
}
#JsonAnySetter
public void setUnknownFields(String name, Object value) {
others.put(name, value);
}
}
How can I serialize a TreeSet properly? In order to give you an idea of what's not working I've set up this little demo project. The main goal is to print a JSON string of my QData object.
App.java
package de.company.gsonserializer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
public class App
{
public static void main( String[] args )
{
QData qdata = new QData();
ArrayList<LData> arrayList = new ArrayList<LData>(1);
LData l = new LData();
Map<String, String> unsortedBuabList = new HashMap<String, String>();
for (int i = 0; i < 5; i++) {
unsortedBuabList.put("Key-" + i, "Value" + i);
}
SortedSet<Map.Entry<String, String>> sortedBuabList = new TreeSet<Map.Entry<String, String>>(
new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> e1, Map.Entry<String, String> e2) {
return e1.getValue().compareTo(e2.getValue());
}
});
sortedBuabList.addAll(unsortedBuabList.entrySet());
l.setBuabList(sortedBuabList);
arrayList.add(l);
qdata.setLocations(arrayList);
System.out.println( qdata.toString() );
}
}
QData.java
package de.it2media.gsonserializer;
import java.util.ArrayList;
import com.google.gson.Gson;
public class QData {
private ArrayList<LData> locations = new ArrayList<LData>(0);
public ArrayList<LData> getLocations() {
return locations;
}
public void setLocations(ArrayList<LData> locations) {
this.locations = locations;
}
#Override
public String toString(){
Gson gson = new Gson();
String thisObj = gson.toJson(this);
return thisObj;
}
}
LData.java
package de.it2media.gsonserializer;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Map.Entry;
public class LData {
private SortedSet<Entry<String, String>> buabList = new TreeSet<Map.Entry<String, String>>();
public SortedSet<Entry<String, String>> getBuabList() {
return buabList;
}
public void setBuabList(SortedSet<Entry<String, String>> buabList) {
this.buabList = buabList;
}
}
The output: {"locations":[{"buabList":[{},{},{},{},{}]}]}
Expected output would be something like: {"locations":[{"buabList":[{"key":"Key-0","value":"Value0"},{"key":"Key-1","value":"Value1"},{"key":"Key-2","value":"Value2"},{"key":"Key-3","value":"Value3"},{"key":"Key-4","value":"Value4"}]}]}
Do you might know why GSON is not working as I'd expect it to work?
Thanks for any help, highly appreciated!
The problem you are running into has nothing to do with the TreeSet, but rather with the fact that GSON does not know how to serialize a map Entry in the way that you would like. You therefore need to write a custom serializer for it, which looks something like this:
public static class EntrySerializer implements JsonSerializer<Entry<String, String>> {
#Override
public JsonElement serialize(Entry<String, String> entry, Type typeOfSrc, JsonSerializationContext context) {
JsonElement serializedKey = context.serialize(entry.getKey());
JsonElement serializedValue = context.serialize(entry.getValue());
JsonObject jsonObject = new JsonObject();
jsonObject.add("key", serializedKey);
jsonObject.add("value", serializedValue);
return jsonObject;
}
}
When you create the Gson object, you then need to register this custom serializer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Entry.class, new EntrySerializer())
.create();
You can read more about custom serializers and deserializers in the GSON documentation.
Using Spring Integration and I have a json string (see below) and the following code:
public SomethingBean convert(Message<?> inMessage) {...}
Json string
{
"addressIdentification": {
"identifierType": "nemtom",
"addressIdentifier": "eztse"
},
"postcode": "BH1EH",
"country": "5"
}
I'd like to use the following method signature:
public SomethingBean convert(Message<Map<String, ?>> inMessage) {...}
Is it possible to convert the json string to Map automatically?
Thanks,
V.
Just use Spring Integration out of the box component:
<json-to-object-trnsfrormer type="java.util.Map"/>
before your SomethingBean invocation.
Use any JSON parsing library such as GSON or Jackson and convert it into Java Object.
GSON:
String jsonString = "{\"addressIdentification\":{\"identifierType\":\"nemtom\",\"addressIdentifier\":\"eztse\"},\"postcode\":\"BH1EH\",\"country\":\"5\"}";
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));
for(Map.Entry<String, Object> entry:data.entrySet()){
System.out.println(entry.getKey()+":"+entry.getValue());
}
Jackson:
String jsonString = "{\"addressIdentification\":{\"identifierType\":\"nemtom\",\"addressIdentifier\":\"eztse\"},\"postcode\":\"BH1EH\",\"country\":\"5\"}";
JSONObject jsonObject = new JSONObject(jsonString);
System.out.println(jsonObject);
JSONObject addressIdentification = jsonObject.getJSONObject("addressIdentification");
System.out.println("identifierType:" + addressIdentification.get("identifierType"));
System.out.println("addressIdentifier:" + addressIdentification.get("addressIdentifier"));
System.out.println("postcode:" + jsonObject.get("postcode"));
System.out.println("country:"+jsonObject.get("country"));
output:
identifierType:nemtom
addressIdentifier:eztse
postcode:BH1EH
country:5
Read more...
You can do with below code
package com.mboot.generator.models;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Container for all request keys and values values maybe single value or list
*/
public class OptionParameter {
private Map<OptionKey, Object> parameter = new HashMap<>();
public OptionParameter add(OptionKey OptionKey, Object value) {
if (value != null && parameter.get(OptionKey) == null)
parameter.put(OptionKey, value);
return this;
}
public Object get(OptionKey OptionKey) {
return parameter.get(OptionKey);
}
public void iterator(ParameterIterator iterator) {
parameter.entrySet().stream().sorted((k, v) -> k.getKey().name().compareTo(v.getKey().name()))
.forEach((e) -> iterator.parameter(e.getKey(), e.getValue()));
}
public interface ParameterIterator {
void parameter(OptionKey key, Object value);
}
#Override
public String toString() {
return "OptionParameter{" + "parameter=" + parameter + '}';
}
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
OptionParameter parameter1 = (OptionParameter) o;
return Objects.equals(parameter, parameter1.parameter);
}
#Override
public int hashCode() {
return Objects.hash(parameter);
}
}
And your message converter should be like below
package com.mboot.generator.utils;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.mboot.generator.models.OptionKey;
import lombok.SneakyThrows;
public class StringToMapConverter implements Converter<String, Map<OptionKey, Object>> {
#Autowired
private ObjectMapper objectMapper;
TypeFactory factory = TypeFactory.defaultInstance();
#Override
#SneakyThrows
public Map<OptionKey, Object> convert(String source) {
// Map<OptionKey, Object> result = objectMapper.readValue(source,
// factory.constructMapType(Map.class, OptionKey.class, Object.class));
JavaType javaType = objectMapper.getTypeFactory().constructParametrizedType(Map.class, OptionKey.class, Object.class);
return objectMapper.readValue(source,javaType);
}
}
I have a class which looks like this:
#JsonFormat(shape=JsonFormat.Shape.OBJECT)
public class MyMap implements Map<String, String>
{
protected Map<String, String> myMap = new HashMap<String, String>();
protected String myProperty = "my property";
public String getMyProperty()
{
return myProperty;
}
public void setMyProperty(String myProperty)
{
this.myProperty = myProperty;
}
//
// java.util.Map mathods implementations
// ...
}
And a main method with this code:
MyMap map = new MyMap();
map.put("str1", "str2");
ObjectMapper mapper = new ObjectMapper();
mapper.getDeserializationConfig().withAnnotationIntrospector(new JacksonAnnotationIntrospector());
mapper.getSerializationConfig().withAnnotationIntrospector(new JacksonAnnotationIntrospector());
System.out.println(mapper.writeValueAsString(map));
When executing this code I'm getting the following output: {"str1":"str2"}
My question is why the internal property "myProperty" is not serialized with the map?
What should be done to serialize internal properties?
Most probably you will end up with implementing your own serializer which will handle your custom Map type. Please refer to this question for more information.
If you choose to replace inheritance with composition, that is to make your class to include a map field not to extend a map, then it is pretty easy to solve this using the #JsonAnyGetter annotation.
Here is an example:
public class JacksonMap {
public static class Bean {
private final String field;
private final Map<String, Object> map;
public Bean(String field, Map<String, Object> map) {
this.field = field;
this.map = map;
}
public String getField() {
return field;
}
#JsonAnyGetter
public Map<String, Object> getMap() {
return map;
}
}
public static void main(String[] args) throws JsonProcessingException {
Bean map = new Bean("value1", Collections.<String, Object>singletonMap("key1", "value2"));
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(map));
}
}
Output:
{"field":"value1","key1":"value2"}
I want to convert a HashMap in a POJO class to XML. I tried using the XmlAdapter but it results in only the key and value pairs of the HashMap being the attributes of the XML Elements. I need the Key to be the Element itself and the value of the HashMap to be the value of the element. For instance, I need the following XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cart>
<supervisor_id>555</supervisor_id>
<payments>
<payment sequence="1">
<amount>123.45</amount>
<billing_method>12345</billing_method>
<form>card</form>
<delivery_mode>Q</delivery_mode>
</payment>
<payment sequence="2">
<amount>123.45</amount>
<person_id>2333</person_id>
<form>cash</form>
<delivery_mode>Q</delivery_mode>
</payment>
</payments>
</cart>
I created the following classes: MyMapType holds a list of MyMapEntryType class which has two fields namely Key and Value. How do I change the Key element to be #XmlElement and assign the value field to the Key field?
Here are my source files.
MyMapType.java
import java.util.ArrayList;
import java.util.List;
public class MyMapType {
private List<MyMapEntryType> entry = new ArrayList<MyMapEntryType>();
public List<MyMapEntryType> getEntry() {
return entry;
}
public void setEntry(List<MyMapEntryType> entry) {
this.entry = entry;
}
}
MyMapEntryType.java
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
#XmlAccessorType(XmlAccessType.FIELD)
public class MyMapEntryType {
#XmlAttribute
private String key;
#XmlValue
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;
}
}
Please also find the adapter class:
MyMapAdapter.java
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class MyMapAdapter extends XmlAdapter<MyMapType, Map<String, String>> {
#Override
public MyMapType marshal(Map<String, String> map) throws Exception {
MyMapType myMapType = new MyMapType();
for(Entry<String, String> entry : map.entrySet()) {
MyMapEntryType myMapEntryType = new MyMapEntryType();
myMapEntryType.setKey(entry.getKey());
myMapEntryType.setValue(entry.getValue());
myMapType.getEntry().add(myMapEntryType);
}
return myMapType;
}
#Override
public Map<String, String> unmarshal(MyMapType map) throws Exception {
HashMap<String, String> hashMap = new HashMap<String, String>();
for(MyMapEntryType myEntryType : map.getEntry()) {
hashMap.put(myEntryType.getKey(), myEntryType.getValue());
}
return hashMap;
}
}
This is the class which has the HashMap field:
XmlElementMap.java
#XmlAccessorType(XmlAccessType.FIELD)
public class XmlElementMap {
#XmlAttribute(name="sequence")
private int sequence;
#XmlJavaTypeAdapter(MyMapAdapter.class)
private Map<String, String> map = new HashMap<String, String>();
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
Please advise on how to achieve this.
Regards,
-Anand
Currently it produces the following output:
I have the same requirement "I need the Key to be the Element itself and the value of the HashMap to be the value of the element".
I didn't use customized adapter, but implemented it by converting the HashMap entries dynamically to a list of JAXBElement objects, and then annotated the list with #XmlAnyElement.
#XmlRootElement(name="root")
public class MyMapType {
#XmlAnyElement
public List<JAXBElement> entries = new ArrayList<JAXBElement>();
public MyMapType() { // JAXB required
}
public MyMapType(Map<String, String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
entries.add(new JAXBElement(new QName(entry.getKey()),
String.class, entry.getValue()));
}
}
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(MyMapType.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
MyMapType mt = new MyMapType(map);
marshaller.marshal(mt, System.out);
}
}
The output is,
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<key1>value1</key1>
<key2>value2</key2>
</root>
Note: a marshal/unmarshal example for Map instance can be found here: Dynamic tag names with JAXB.