I want to provide a POST servlet that takes the following JSON content:
{
"name": John
"age": 25,
"some": "more",
"params: "should",
"get": "mapped"
}
Two of those properties should be explicit mapped to defined parameters. All other parameters should go into a Map<String, String>.
Question: how can I let Spring map them directly into the map of the bean?
#RestController
public void MyServlet {
#PostMapping
public void post(#RequestBody PostBean bean) {
}
}
public class PostBean {
private String name;
private String age;
//all other json properties should go here
private Map<String, String> map;
}
public class PostBean {
private Map<String, String> map;
#JsonAnyGetter
public Map<String, String> getMap() {
return map;
}
#JsonAnySetter
public void setMap(String name, String value) {
if (this.map == null) map = new HashMap<>();
this.map.put(name, value);
}
}
Related
I have a Class like this:
public class MyClass
{
private int id;
private Map<String, String> myMap;
public Map<String, String> getMyMap()
{
return myMap;
}
public void setMyMap(Map<String, String> myMap)
{
this.myMap = myMap;
}
}
I added new setter method(overloading) because i didn't want to do set HashMap directly, and that's what you see now :
public class MyClass
{
private int id;
private Map<String, String> myMap;
public Map<String, String> getMyMap()
{
return myMap;
}
public void setMyMap(Map<String, String> myMap)
{
this.myMap = myMap;
}
public void setMyMap(String key , String value)
{
setMyMap(new HashMap<>(){{put(key, value);}});
}
}
But because i used new HashMap<>(){{put(key, value);}} keyword every time i use this method , it create new Map and last items deleted .
So i have 2 question:
1-correct solution for set items by 2nd setter method
2-how i could use this setter method for multiple put's for this situations:
MyClass.setMyMap(new HashMap<>()
{{
put("title", title);
put("id", id);
}});
Thank you guys for your time .
It depends on what your class does. But in general, I would not expose a setter for a map field.
It makes sense to add a constructor with a map argument, then do something like this:
public class MyClass
{
private final int id;
private final Map<String, String> myMap;
public MyClass(int id, Map<String, String> myMap) {
this.id = id;
this.myMap = myMap;
}
public Map<String, String> getMyMap()
{
return myMap;
}
public void addPairs(Map<String, String> pairs)
{
myMap.putAll(pairs);
}
public void addPair(String key, String value)
{
myMap.put(key, value);
}
}
Of course, you can expose an additional constructor:
public MyClass(int id) {
this.id = id;
this.myMap = new HashMap<>();
}
Try some thing like this:
public void setMyMap(String key , String value) {
if(myMap == null)
myMap = new HashMap<String, String>();
myMap.put(key, value);
}
You've already declared class field myMap and you want to use it in setMyMap method.
Do null check. If the field is null then create a new map. Then use put method to store data in the map.
I have a Spring Boot application with the following application.yml
Detail_1:
name: X,Y,Z
place: xplace,yplace,zplace
Detail_2:
name: X,Y,Z
place: xplaceanother,yplaceanother,zplaceanother
How can I obtain this map in java:
X {
detail1 :xplace
detail2 :xplaceanother
}
Y {
detail1:yplace,
detail2:yplaceanother
}
Z{
detail1:zplace,
detail2:zplaceanother
}
I have tried the following code :
#Value${detail1.name}
private String names;
#value${detail2.place}
List<Object> Names = Arrays.asList(getNames().split(","));
List<Object> places = Arrays.asList(getPlaces().split(","));
Then I tried to create a map of names and places corresponding to detail 1
similarly I fetched names and places for detail 2
But In this case i end up with 2 maps , one for detail 1 and one for detail 2.
I need to create a single map.
You need to use #ConfigurationProperties annotation
The following URLs provide good examples in both .properties and .yml format:
https://www.mkyong.com/spring-boot/spring-boot-configurationproperties-example/
https://www.baeldung.com/configuration-properties-in-spring-boot
Please update your config like below in application.yml
map:
detail1:
name:X,Y,Z
place:xplace,yplace,zplace
detail2:
name:X,Y,Z
place:xplaceanother,yplaceanother,zplaceanother
and then configure the property as below,
DetailConfig.java
#Component
#ConfigurationProperties(prefix="map")
public class DetailConfig {
private Map<String, Object> detail1;
private Map<String, Object> detail2;
public Map<String, Object> getDetail1() {
return detail1;
}
public void setDetail1(Map<String, Object> detail1) {
this.detail1 = detail1;
}
public Map<String, Object> getDetail2() {
return detail2;
}
public void setDetail2(Map<String, Object> detail2) {
this.detail2 = detail2;
}
}
You can use the following pojo for property;
public class Detail {
private List<String> name;
private List<String> place;
public Map<String, String> getNamePlaceMap() {
return IntStream.range(0, name.size()).boxed()
.collect(Collectors.toMap(i -> name.get(i), i -> place.get(i)));
}
// getters/setters
}
and use the following configuration to get properties into context;
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "detail-1")
public Detail detailOne() {
return new Detail();
}
#Bean
#ConfigurationProperties(prefix = "detail-2")
public Detail detailTwo() {
return new Detail();
}
}
and autowire them and pass them to the logic where that map is created;
#Service
public class TestService {
#Autowired
private Detail detailOne;
#Autowired
private Detail detailTwo;
public void test() {
System.out.println(createSpecialMap(detailOne, detailTwo));
}
private static Map<String, Map<String, String>> createSpecialMap(Detail detailOne, Detail detailTwo) {
Map<String, Map<String, String>> resultMap = new HashMap<>();
detailOne.getNamePlaceMap().forEach((key, value) -> {
Map<String, String> subMap = resultMap.getOrDefault(key, new HashMap<>());
subMap.put("detail1", value);
resultMap.put(key, subMap);
});
detailTwo.getNamePlaceMap().forEach((key, value) -> {
Map<String, String> subMap = resultMap.getOrDefault(key, new HashMap<>());
subMap.put("detail2", value);
resultMap.put(key, subMap);
});
return resultMap;
}
}
results in;
{
X={detail1=xplace, detail2=xplaceanother},
Y={detail1=yplace, detail2=yplaceanother},
Z={detail1=zplace, detail2=zplaceanother}
}
Or better in readability, using a Letter class;
public class Letter {
private String name;
private String detail1;
private String detail2;
public Letter(String name, String detail1, String detail2) {
this.name = name;
this.detail1 = detail1;
this.detail2 = detail2;
}
// getters/setters
}
doing the following;
private static List<Letter> createList(Detail detailOne, Detail detailTwo) {
List<Letter> resultList = new ArrayList<>();
Map<String, String> detailOneMap = detailOne.getNamePlaceMap();
Map<String, String> detailTwoMap = detailTwo.getNamePlaceMap();
Set<String> keySet = new HashSet<>();
keySet.addAll(detailOneMap.keySet());
keySet.addAll(detailTwoMap.keySet());
return keySet.stream()
.map(key -> new Letter(key, detailOneMap.get(key), detailTwoMap.get(key)))
.collect(Collectors.toList());
}
will result in;
[
Letter{name='X', detail1='xplace', detail2='xplaceanother'},
Letter{name='Y', detail1='yplace', detail2='yplaceanother'},
Letter{name='Z', detail1='zplace', detail2='zplaceanother'}
]
which is a better result than a raw map of map...
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);
}
}
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"}
Lets say your Json consist of a bunch of freeform pairs
"config": {
"k1": "abc",
"k2": "xyz"
},
Rules
I don't know how many keys i'll have
All values will be Strings
I'd like for map to be addressable by a key
Where some of the values are Strings and others are Numbers. I was thinking that HashMap
public class Outer {
private Config config = new Config();
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
public class Config {
private Map<String, String> map = new HashMap<>();
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
}
When using testing this, i see that getConfig() returns a non-null value. But when i get to getMap() i get null
Please help me understand what am i missing here.
You should use just:
public class Outer {
private Map<String, String> config = new HashMap<>();
public Map<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
}
In the class model you wrote, you are describing a JSON like this:
"config": {
"map": {
"k1": "abc",
"k2": "xyz"
}
},
but as you can see, that's not what you want...
You have to realize that the field config in your JSON is not an object that contains a field called map that represents a Map... but the field config represents itself a Map!