How to create Map from yaml file with Jackson? - java

This is the class:
public class YamlMap {
Map<String, String> mp = new HashMap<>();
String get(String key) {
return this.mp.get(key);
}
}
and this is the props.yml:
mp:
key1: ok
key2: no
When I run:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.findAndRegisterModules();
YamlMap ym2 = mapper.readValue(new File("src/main/resources/props.yml"), YamlMap.class);
then I get error:
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "YamlMap" (class YamlMap)

A quick solution is to add #JsonProperty("mp") above your field :
public class YamlMap {
#JsonProperty("mp")
Map<String, String> mp;
}
Jackson core annotation names might be misleading but even though this annotation has Json in its' name - it will work. Jackson can be configured to parse different formats like Yaml or CBOR - but still for mapping you would use core annotations which have Json in their names.
Another solution is to create a constructor and use #JsonCreator :
public class YamlMap {
Map<String, String> mp;
#JsonCreator
public YamlMap(#JsonProperty("mp") Map<String, String> mp) {
this.mp = mp;
}
}

Related

Using Jackson to deserialize JSON to Map

I am trying to find an easy way to deserialize the following JSON to an OpeningHours Object containing a Map<String, List<String>> which contains the days as keys and the opening hours as a list.
I am using Jackson and created my own deserializer #JsonDeserialize(using = MyDeserializer.class)
The problem is that I need to check which of the different JSON nodes is present to set the keys for the map.
Is there an easy alternative solution?
{
"OpeningHours": {
"Days": {
"Monday": {
"string": [
"09:00-13:00",
"13:30-18:00"
]
},
"Tuesday": {
"string": [
"09:00-13:00",
"13:30-18:00"
]
}
}
}
}
Jackson can deserialize any Json into Map<String, Object>, that may contain nested maps for nested json objects. all that is needed is casting:
String json = ...
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> openingHours = (Map<String, Object>)mapper.readValue(json, Map.class);
Map<String, List<String>> days = (Map<String, List<String>>)((Map<String, Object>)openingHours.get("OpeningHours")).get("Days");
System.out.println(days);
output:
{Monday={string=[09:00-13:00, 13:30-18:00]}, Tuesday={string=[09:00-13:00, 13:30-18:00]}}
You could just deserialize it to a data structure that represents the JSON like
#Data
public class TempStore {
private List<DayTempStore> days;
}
#Data
public class DaytempStore {
private String[] string;
}
and just transform this to a Map> leaving the hassle with Nodes and Checks to Jackson.
I think you are making the issue is a bit more complex than it is. You don't need a custom deserializer in this case. All you need is this:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModules(new JavaTimeModule());
ObjectReader obrecjReader = objectMapper.reader();
Map<String, Object> myMap = objectReader.forType(Map<String,Object>.class).readValue(jsonString);

JsonAnyGetter / JsonAnySetter the resulting JSON has doubled values

I need to write a JSON string that follows this basic format:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"payload":{"test1":"test1"}
}]
}
I'm using the following class to present the data, and an instance of this class is serialized with the Jackson ObjectMapper.
#Data
public class Angebot {
private String instanzId;
private List<BuchungsKontext> buchungsKontextList;
private Map<String, Object> payload = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> any() {
return payload;
}
#JsonAnySetter
public void set(String name, Object value) {
payload.put(name, value);
}
}
If I serialize an instance of this class as-is, the resulting JSON will be something like this:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"payload":{"test1":"test1"},
"test1":"test1"
}]
}
As you can see the data of "payload" is doubled as it's own element and I don't have any idea why.
Thanks in advance for your attention and advice.
It seems like you want to serialize payload as a normal map. So if you don't want it there twice then you should not have the any() method, just have a regular getter method for payload.
The any() method can be used in case you want to serialize all the items from the payload map to appear like they are properties of Angebot class. Then you would use the any method, and not have a getter for payload.
Your JSON would come out like this:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"test1":"test1"
}]
}
And it will look like test1 is a variable of the Angebot class.
It is because of any() getter. Just remove it:
#Data
public class Angebot {
private String instanzId;
private List<BuchungsKontext> buchungsKontextList;
private Map<String, Object> payload = new HashMap<String, Object>();
// #JsonAnyGetter
// public Map<String, Object> any() {
// return payload;
// }
#JsonAnySetter
public void set(String name, Object value) {
payload.put(name, value);
}
}
payload is class property. It gets naturally de-serialized because of #Data annotation. any() getter creates duplicity.

Loading Map<String List<String>> from properties file in spring [duplicate]

Is it possible to use Spring #Value, to map values from properties file to the HashMap.
Currently I have something like this, and mapping one value is not a problem.
But I need to map custom values in HashMap expirations.
Is something like this possible?
#Service
#PropertySource(value = "classpath:my_service.properties")
public class SomeServiceImpl implements SomeService {
#Value("#{conf['service.cache']}")
private final boolean useCache = false;
#Value("#{conf['service.expiration.[<custom name>]']}")
private final HashMap<String, String> expirations = new HashMap<String, String>();
Property file: 'my_service.properties'
service.cache=true
service.expiration.name1=100
service.expiration.name2=20
Is it posible to map like this key:value set
name1 = 100
name2 = 20
You can use the SPEL json-like syntax to write a simple map or a map of list in property file.
simple.map={'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}
map.of.list={\
'KEY1': {'value1','value2'}, \
'KEY2': {'value3','value4'}, \
'KEY3': {'value5'} \
}
I used \ for multiline property to enhance readability
Then, in Java, you can access and parse it automatically with #Value like this.
#Value("#{${simple.map}}")
Map<String, String> simpleMap;
#Value("#{${map.of.list}}")
Map<String, List<String>> mapOfList;
Here with ${simple.map}, #Value gets the following String from the property file:
"{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}"
Then, it is evaluated as if it was inlined
#Value("#{{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}}")
You can learn more in the official documentation
Is it possible to use Spring #Value, to map values from properties file to the HashMap?
Yes, it is. With a little help of code and Spel.
Firstly, consider this singleton Spring-bean (you should scan it):
#Component("PropertySplitter")
public class PropertySplitter {
/**
* Example: one.example.property = KEY1:VALUE1,KEY2:VALUE2
*/
public Map<String, String> map(String property) {
return this.map(property, ",");
}
/**
* Example: one.example.property = KEY1:VALUE1.1,VALUE1.2;KEY2:VALUE2.1,VALUE2.2
*/
public Map<String, List<String>> mapOfList(String property) {
Map<String, String> map = this.map(property, ";");
Map<String, List<String>> mapOfList = new HashMap<>();
for (Entry<String, String> entry : map.entrySet()) {
mapOfList.put(entry.getKey(), this.list(entry.getValue()));
}
return mapOfList;
}
/**
* Example: one.example.property = VALUE1,VALUE2,VALUE3,VALUE4
*/
public List<String> list(String property) {
return this.list(property, ",");
}
/**
* Example: one.example.property = VALUE1.1,VALUE1.2;VALUE2.1,VALUE2.2
*/
public List<List<String>> groupedList(String property) {
List<String> unGroupedList = this.list(property, ";");
List<List<String>> groupedList = new ArrayList<>();
for (String group : unGroupedList) {
groupedList.add(this.list(group));
}
return groupedList;
}
private List<String> list(String property, String splitter) {
return Splitter.on(splitter).omitEmptyStrings().trimResults().splitToList(property);
}
private Map<String, String> map(String property, String splitter) {
return Splitter.on(splitter).omitEmptyStrings().trimResults().withKeyValueSeparator(":").split(property);
}
}
Note: PropertySplitter class uses Splitter utility from Guava. Please refer to its documentation for further details.
Then, in some bean of yours:
#Component
public class MyBean {
#Value("#{PropertySplitter.map('${service.expiration}')}")
Map<String, String> propertyAsMap;
}
And finally, the property:
service.expiration = name1:100,name2:20
It's not exactly what you've asked, because this PropertySplitter works with one single property that is transformed into a Map, but I think you could either switch to this way of specifying properties, or modify the PropertySplitter code so that it matches the more hierarchical way you desire.
From Spring 4.1.x ( I can't remember specific version though ), you can do something like
#Value("#{${your.properties.key.name}}")
private Map<String, String> myMap;
where your.properties.key.name in your properties file should be something like
your.properties.key.name={\
name1 : 100, \
name2 : 200 \
}
Just make sure that you should create PropertySourcesPlaceholderConfigurer bean to make it work both in your app and if you are writing any unit test code to test your code, otherwise ${...} placeholder for the property value won't work as expected and you'll see some weird SpringEL errors.
The quickest Spring Boot based solution I can think of follows. In my particular example I am migrating data from one system to another. That is why I need a mapping for a field called priority.
First I've created the properties file (priority-migration.properties) like such:
my.prefix.priority.0:0
my.prefix.priority.10:1
my.prefix.priority.15:2
my.prefix.priority.20:2
another.prefix.foo:bar
and put it on the classpath.
Assuming you want to use the map in a spring managed bean/component, annotate your class with:
#Component
#PropertySource("classpath:/priority-migration.properties")
What you actually want in your map is of course only the key/value pairs which are prefixed with my.prefix, i.e. this part:
{
0:0
10:1
15:2
20:2
}
To achieve that you need to annotate your component with
#ConfigurationProperties("my.prefix")
and create a getter for the priority infix. The latter proved to be mandatory in my case (although the Sring Doc says it is enough to have a property priority and initialize it with a mutable value)
private final Map<Integer, Integer> priorityMap = new HashMap<>();
public Map<Integer, Integer> getPriority() {
return priorityMap;
}
In the End
It looks something like this:
#Component
#ConfigurationProperties("my.prefix")
#PropertySource("classpath:/priority-migration.properties")
class PriorityProcessor {
private final Map<Integer, Integer> priorityMap = new HashMap<>();
public Map<Integer, Integer> getPriority() {
return priorityMap;
}
public void process() {
Integer myPriority = priorityMap.get(10)
// use it here
}
}
I make one solution inspired by the previous post.
Register property file in the Spring configuration:
<util:properties id="myProp" location="classpath:my.properties"/>
And I create component:
#Component("PropertyMapper")
public class PropertyMapper {
#Autowired
ApplicationContext applicationContext;
public HashMap<String, Object> startWith(String qualifier, String startWith) {
return startWith(qualifier, startWith, false);
}
public HashMap<String, Object> startWith(String qualifier, String startWith, boolean removeStartWith) {
HashMap<String, Object> result = new HashMap<String, Object>();
Object obj = applicationContext.getBean(qualifier);
if (obj instanceof Properties) {
Properties mobileProperties = (Properties)obj;
if (mobileProperties != null) {
for (Entry<Object, Object> e : mobileProperties.entrySet()) {
Object oKey = e.getKey();
if (oKey instanceof String) {
String key = (String)oKey;
if (((String) oKey).startsWith(startWith)) {
if (removeStartWith)
key = key.substring(startWith.length());
result.put(key, e.getValue());
}
}
}
}
}
return result;
}
}
And when I want to map all properties that begin with specifix value to HashMap, with #Value annotation:
#Service
public class MyServiceImpl implements MyService {
#Value("#{PropertyMapper.startWith('myProp', 'service.expiration.', true)}")
private HashMap<String, Object> portalExpirations;
Solution for pulling Map using #Value from application.yml property coded as multiline
application.yml
other-prop: just for demo
my-map-property-name: "{\
key1: \"ANY String Value here\", \
key2: \"any number of items\" , \
key3: \"Note the Last item does not have comma\" \
}"
other-prop2: just for demo 2
Here the value for our map property "my-map-property-name" is stored in JSON format inside a string and we have achived multiline using \ at end of line
myJavaClass.java
import org.springframework.beans.factory.annotation.Value;
public class myJavaClass {
#Value("#{${my-map-property-name}}")
private Map<String,String> myMap;
public void someRandomMethod (){
if(myMap.containsKey("key1")) {
//todo...
} }
}
More explanation
\ in yaml it is Used to break string into multiline
\" is escape charater for "(quote) in yaml string
{key:value} JSON in yaml which will be converted to Map by #Value
#{ } it is SpEL expresion and can be used in #Value to convert json int Map or Array / list Reference
Tested in a spring boot project
Use the same variable name as the Yaml name
Eg:
private final HashMap<String, String> expiration
instead of
private final HashMap<String, String> expirations
Or something similar to this in properties file
org.code=0009,0008,0010
org.code.0009.channel=30,40
org.code.0008.channel=30,40
org.code.0010.channel=30,40
in Java, read org.code and then loop thru each org.code and build org.code..channel and put it into a map....

#jsonanygetter doesn't work

Assume, I have the following class:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectOfMonitoring {
private BigInteger id;
private Map<String, Object> properties = new HashMap<>();
#JsonAnySetter
public void add(String key, Object value) {
properties.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
I test it in the following code:
ObjectOfMonitoring objectOfMonitoring = new ObjectOfMonitoring();
objectOfMonitoring.setId(BigInteger.ONE);
objectOfMonitoring.add("key1", "value1");
String jsonInString = mapper.writeValueAsString(objectOfMonitoring);
System.out.println(jsonInString);
I want to get result:
{"id":1,"key2":"value2","key1":"value1"}
But actual result is:
{"id":1,"properties":{"key2":"value2","key1":"value1"}}
What do I do incorrectly? And how to get the expected result?
Make sure that ObjectMapper and #JsonAnySetter/#JsonAnyGetter annotations are from the same packages.
All should be:
either from org.codehaus.jackson - which is the older
version of jackson
or from com.fasterxml.jackson - which
is newer (Jackson has moved from Codehaus when releasing Jackson 2 -
see here)
If you have both dependencies in your project and you are using them interchangeably you may have such hard to notice problems.
The best would be to just get rid of org.codehaus.jackson from your project dependencies.
Might be late to the party but thought of posting the answer as it can be helpful to someone in the future.
This is the sample code which you can configure accordingly for your class.
The main class which will read the JSON and associates with the class:
public class Main {
public static void main(String[] args) throws Exception {
File jsonFile = new File("test.json").getAbsoluteFile();
final ObjectMapper mapper = new ObjectMapper();
MyObject myObject = mapper.readValue(jsonFile, MyPojo.class);
}
}
Your custom POJO:
class MyObject {
private int id;
private Map<String,String> customField;
//Getter and Setter for POJO fields ommited
#JsonAnySetter
public void setCustomField(String key, Object value) {
System.out.println(" Key : " + key + " Value : " + value);
if(customField == null){
customField = new HashMap<String,Object>();
}
customField.put(key,value);
}
#JsonAnyGetter
public Map<String,String> getCustomField(){
return customField;
}
}
This will work if even for duplicate keys within the JSON if you are using the readValue from ObjectMapper but if you are using the treeToValue method from ObjectMapper then this would fail for the duplicate keys and you will have only the last value for the duplicated field. I am still figuring out a way to access the duplicate field while using the treeToValue method

Jackson JSON serialization in Java - write k/v pairs to root instead of nested in property name

I need to write a JSON string that follows this basic format:
{
"xmlns": {
"nskey1" : "nsurl1",
"nskey2" : "nsurl2"
},
"datakey1": "datavalue1",
"datakey2": "datavalue2"
}
I'm using the following class to present the data, and an instance of this class is serialized with the Jackson ObjectMapper.
public class PayloadData {
public Map<String, String> payloadData = new TreeMap<String, String>();
#JsonProperty("xmlns")
public Map<NamespaceEnum, String> namespaces = new TreeMap<NamespaceEnum, String>();
#JsonAnyGetter
public Map<String, String> getPayloadData() {
return payloadData;
}
}
If I serialize an instance of this class as-is, the resulting JSON will be something like this:
{
"xmlns": {
"nskey1" : "nsurl1",
"nskey2" : "nsurl2"
},
"payloadData": {
"datakey1": "datavalue1",
"datakey2": "datavalue2"
},
"datakey1": "datavalue1",
"datakey2": "datavalue2"
}
That makes sense based on the naming conventions, but I'm looking for a method to have the payloadData map placed in the JSON's root context without the duplicate that contains the property identifier and nesting. I've tried a lot of annotations in various forms; I've tried disabling the ObjectMapper wrap_root_value SerializationFeature; I honestly feel like I've tried just about everything. So before I throw a computer out the window, I'm asking for a second (and beyond) set of eyes to help point out what must be painfully obvious.
Thanks in advance for your attention and advice.
edit: updated the actual output JSON I see now. The data is being duplicated, and I'd like to remove the duplicate that has the nested property.
The problem is that you have 2 accessors exposed for PayloadData: the public property and the getter, so it is being serialized twice. If it is possible, I would recommend restructuring your data class to be immutable. For example:
public class PayloadData {
private final Map<String, String> payloadData;
private final Map<NamespaceEnum, String> namespaces;
#JsonCreator
public PayloadData(#JsonProperty("xmlns") Map<NamespaceEnum, String> namespaces,
#JsonProperty("payloadData") Map<String, String> payloadData) {
this.namespaces = namespaces;
this.payloadData = payloadData;
}
#JsonAnyGetter
public Map<String, String> getPayloadData() {
return payloadData;
}
#JsonProperty("xmlns")
public Map<NamespaceEnum, String> getNamespaces() {
return namespaces;
}
}
This will give you the desired output with out any configuration of the ObjectMapper.
You can parse your json data to a HashMap not a class object:
public HashMap<String, Object> testJackson(String data) throws IOException {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
TypeReference<HashMap<String,Object>> typeRef
= new TypeReference<HashMap<String,Object>>() {};
HashMap<String,Object> o = mapper.readValue(data, typeRef);
return o
}
Get JSON data from a HashMap:
public String getJsonFromMap(HashMap<String, Object> data) {
return new ObjectMapper().writeValueAsString(data);
}

Categories