Jackson deserialization of YAML file into Map (with no custom deserializer) - java

I'm trying to load a YAML file into an instance of a Map. Is there a way to load it without defining the custom deserializer (e.g. using annotations?)?
This is an example of my YAML file:
- site: First Site
url: some_url
username: some_name
password: some_password
- site: Second Site
url: its_url
username: its_name
password: its_password
This is the java bean class to deserialize one "site configuration" into (generated by http://www.jsonschema2pojo.org/):
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.commons.lang3.builder.ToStringBuilder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"site",
"url",
"username",
"password"
})
public class SiteConfiguration {
#JsonProperty("site")
private String site;
#JsonProperty("url")
private String url;
#JsonProperty("username")
private String username;
#JsonProperty("password")
private String password;
/**
* No args constructor for use in serialization
*
*/
public SiteConfiguration() {
}
/**
*
* #param site
* #param username
* #param password
* #param url
*/
public SiteConfiguration(String site, String url, String username, String password) {
super();
this.site = site;
this.url = url;
this.username = username;
this.password = password;
}
#JsonProperty("site")
public String getSite() {
return site;
}
#JsonProperty("site")
public void setSite(String site) {
this.site = site;
}
#JsonProperty("url")
public String getUrl() {
return url;
}
#JsonProperty("url")
public void setUrl(String url) {
this.url = url;
}
#JsonProperty("username")
public String getUsername() {
return username;
}
#JsonProperty("username")
public void setUsername(String username) {
this.username = username;
}
#JsonProperty("password")
public String getPassword() {
return password;
}
#JsonProperty("password")
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return new ToStringBuilder(this).append("site", site).append("url", url).append("username", username).append("password", password).toString();
}
}
And finally, this is the code that deserializes the YAML above into Map.
private static Map<String,SiteConfiguration> sites;
public static SiteConfiguration getSiteConfiguration(String key) {
if (sites == null) {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
try {
// todo this finishes on: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token
// sites = mapper.readValue(YamlReader.class.getClass().getResource("/site-configuration.yaml"), new TypeReference<Map<String,SiteConfiguration>>() {});
SiteConfiguration[] sitesArray = mapper.readValue(YamlReader.class.getClass().getResource("/site-configuration.yaml"), SiteConfiguration[].class);
sites = new HashMap<>();
for (SiteConfiguration site : sitesArray) { //todo is there Jackson built-in deserialization?
sites.put(site.getSite(), site);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return sites.get(key);
}
As you can see, I'm doing it in two steps. First I deserialize the file into an array of SiteConfiguration instances, then I put those into a Map where site field represents the map key.
Is there a way to do the same while omitting the array of SiteConfiguration instances? Is the only way to do it using a custom deserializer?

Change the structure of your YAML. If you want to deserialize your data as a map, it needs to start as a map in the YAML file. You have a sequence of mappings in your example, but you want a mapping of mappings.
https://yaml.org/spec/1.2/spec.html#id2759963
Example 2.4. Sequence of Mappings
(players’ statistics)
-
name: Mark McGwire
hr: 65
avg: 0.278
-
name: Sammy Sosa
hr: 63
avg: 0.288
Example 10.1. !!map Examples
Block style: !!map
Clark : Evans
Ingy : döt Net
Oren : Ben-Kiki
Try this instead:
First Site:
site: First Site
url: some_url
username: some_name
password: some_password
Second Site:
site: Second Site
url: its_url
username: its_name
password: its_password
This structure works with Jackson 2.9.9 with no custom deserializers.

Related

How to read Azure app config values from Java SpringBoot app

I have a Springboot app that is trying to connect and read values from an app config resource.
Some keys in the Azure app config (which I am unable to change) are in this format
`
/application/config.datasource.jdbc-url
/application/config.datasource.password
/application/config.datasource.username`
I have a config Java class with prefix ("config"), but I don't know what member variables I should have in order to access "datasource.jdbc-url", "datasource.password" etc.
If the app config was just /application/config.username
then I could just use the below in my Java class
String username;
but for some of the configs that include multiple dots and some dashes (which Java identifiers can't have), how can I read the values?
Thank you!
Your use case is similar to this sample azure-spring-cloud-starter-appconfiguration-config-sample, please try to define the properties class to bind the configurations in Azure App Configuration.
Adding to #Moarchy Chan#ConfigurationProperties(prefix = "config.datasource")
In Azure portal>App configuration> Configuration explorer, I have created keys and values for Username, Password and Jdbc url.
This is my Class UserProperties,
#ConfigurationProperties(prefix = "config.datasource")
public class UserProperties {
private String username;
private String password;
private String jdbcurl;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getJdbcurl() {
return jdbcurl;
}
public void setJdbcurl(String jdbcurl) {
this.jdbcurl = jdbcurl;
}
}
Here is the UserController I have created,
public class UserController {
private final UserProperties properties;
public UserController(UserProperties properties) {
this.properties = properties;
}
#GetMapping("/Anu")
public String getUsername() {
return "username: " + properties.getUsername();
}
#GetMapping("/vyshu")
public String getPassword() {
return "password: " + properties.getPassword();
}
#GetMapping("/priya")
public String getJdbcurl() {
return "jdbcurl: " + properties.getJdbcurl();
}
}
This the bootstrap for my code, you should add your connection string in {},
spring.cloud.azure.appconfiguration.stores[0].connection-string= ${APP_CONFIGURATION_CONNECTION_STRING}
This is the output for my Username,
This is the output for my Password,
This is the output for my jdbcurl,
You have two options depending on what you are looking for. You can either use #Value("config.datasource.jdbc-url") or you can use nested properties.
public class Datasource {
private String username;
private String password;
private String jdbcurl;
...
}
#ConfigurationProperties(prefix = "config")
class MyProperties {
private Datasource datasource = new Datasource();
...
}

sentence case json property is not mapping with pojo class in rest api

i am making a post request to rest api from postman with body as json :
{
"username":"ramakanta",
"password":"test",
"StudentID":"1025"
}
And server side resouces are
public class PushDataEntity {
private String username;
private String password;
private String StudentID;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getStudentID() {
return StudentID;
}
#JsonProperty("StudentID")
public void setStudentID(String StudentID) {
this.StudentID = StudentID;
}
}
#Path("/pushdata")
public class PushDataServiceImpl {
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response dataPush(#Context HttpServletRequest request,PushDataEntity pushDataEntity) throws Exception {
System.out.println(pushDataEntity.getStudentID());//null
}
username and password is mapping but StudentID is not mapping to pojo property. I have tried adding #JsonProperty("StudentID") to setter , getter and property level even , didn't workout. i dont want any change in json, what i should change in java side to make that json property mapped to pojo property. please provide a solution for this. Thanks in advance.
You are sending StudentId, but having StudentID in POJO (the last "d" is different). Either change the JSON in request, or add #JsonProperty("StudentId") annotation to the POJO.
UPDATE
Signature of the method is wrong, there is no such annotation: #ContextHttpServletRequestrequest, it should be
public Response dataPush(#Context HttpServletRequest request,,PushDataEntity pushDataEntity) throws Exception {
You need to put #JsonProperty over the getter function of StudentID.

Jackson serialization with null

Jackson annotations are for serialization but I cannot find the solution to have two different views with different behavior. Having the following json:
{
"username": "username",
"manager": null,
"password": "pwd"
}
I'd like to have to following output in case the first view (public: no null and no sensitive information):
{
"username": "username"
}
For the second view (internal: incoming nulls are showed sensitive information is hashed):
{
"username": "username",
"manager": null,
"password": "hashedValue"
}
The problem is when the optional fields are not provided like in the following json:
{
"password": "pwd"
}
I'd like to have an empty for public and only the hashed password in the internal view:
{
"password": "hashedValue"
}
I have the following class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
private String username;
private String manager;
private String password;
#JsonView(Views.Internal.class)
#JsonSerialize(using = Md5Serializer.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getManager() {
return manager;
}
public void setManager(String manager) {
this.manager = manager;
}
}
This can do most what I'd like to have but it cannot make difference if a value is null because of the default value or because it's coming from the JSON.
An other approach was to use a Map inside the object:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
private Map<String, String> content = new HashMap<>();
#JsonView(Views.Internal.class)
#JsonSerialize(using = Md5Serializer.class)
#JsonIgnore
public String getPassword() {
return this.content.get("password");
}
#JsonSetter
public void setPassword(String password) {
this.content.put("password", password);
}
#JsonIgnore
public String getUsername() {
return this.content.get("username");
}
#JsonSetter
public void setUsername(String username) {
this.content.put("username", username);
}
#JsonIgnore
public String getManager() {
return this.content.get("manager");
}
#JsonSetter
public void setManager(String manager) {
this.content.put("manager", manager);
}
#JsonAnyGetter
public Map<String, String> getContent() {
return this.content;
}
}
In this case the problem that the password is not hashed (and the hashed value cannot be stored here).
If the getters are used and the JsonAnyGetter is removed then the incoming null cannot be serialized as the JsonInclude ignores the field. I was reading a lot that a BeanSerializerModifier can be used but that is also needed to be register in all ObjectMapper instance.
Also I'm not sure that without setting the view I'd like to have only the Public view but currently all non-null values are shown. Any idea how it can be achieved only by annotations?

Mapping JSON into POJO using Gson

I have the following JSON to represent the server response for a salt request:
{
"USER":
{
"E_MAIL":"email",
"SALT":"salt"
},
"CODE":"010"
}
And i tried to map it with the following POJO:
public class SaltPOJO {
private String code = null;
private User user = null;
#Override
public String toString() {
return this.user.toString();
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public class User {
private String e_mail = null;
private String salt = null;
#Override
public String toString() {
return this.e_mail + ": " + this.salt;
}
public String getE_mail() {
return e_mail;
}
public void setE_mail(String e_mail) {
this.e_mail = e_mail;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
}
}
Now everytime i do this:
Gson gson = new Gson();
SaltPOJO saltPojo = gson.fromJson(json.toString(), SaltPOJO.class);
Log.v("Bla", saltPojo.toString());
The saltPojo.toString() is null. How can i map my JSON into POJO using Gson?
Is the order of my variables important for the Gson mapping?
Is the order of my variables important for the Gson mapping?
No, that's not the case.
How can i map my JSON into POJO using Gson?
It's Case Sensitive and the keys in JSON string should be same as variable names used in POJO class.
You can use #SerializedName annotation to use any variable name as your like.
Sample code:
class SaltPOJO {
#SerializedName("CODE")
private String code = null;
#SerializedName("USER")
private User user = null;
...
class User {
#SerializedName("E_MAIL")
private String e_mail = null;
#SerializedName("SALT")
private String salt = null;
You don't have proper mapping between your getter and setter. If you change your json to something like below, it would work:
{
"user":
{
"email":"email",
"salt":"salt"
},
"code":"010"
}
If you are getting json form third party then unfortunately, you would have to change your pojo or you could use adapter.

Generating JSON from POJO for a specific scenario

I have used Jackson and JSONObject to generate a plain JSON - things are fine here. I have a specific case where my pojo looks like below and i need the JSON is the specified format.
package test;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "login")
public class LoginApi implements IRestBean {
private String username;
private String password;
private String sfSessionId;
private String sfServerUrl;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSfSessionId() {
return sfSessionId;
}
public void setSfSessionId(String sfSessionId) {
this.sfSessionId = sfSessionId;
}
public String getSfServerUrl() {
return sfServerUrl;
}
public void setSfServerUrl(String sfServerUrl) {
this.sfServerUrl = sfServerUrl;
}
}
The JSON that i am able to generate looks like this:
{
"username" : null,
"password" : null,
"sfSessionId" : null,
"sfServerUrl" : null
}
But this is not my requirement - i need the JSON in the below format so that my server accepts this as a valid JSON:
{
"#type":"login",
"username":"username#domain.com",
"password":"password",
"sfSessionId":null,
"sfServerUrl":null
}
Please help. Thanks in advance!
Add a private field to the POJO with the type.
#XmlRootElement(name = "login")
public class LoginApi implements IRestBean {
...
#XmlAttribute(name = "type")
private String getJsonType() {
return "login";
}
...
}
Note the use of XmlAttribute to automatically append an "#" to the name.
Change the IRestBean interface to include the #JsonTypeInfo annotation:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="#type")
public interface IRestBean {
...
}
Next, annotate the LoginApi class with #JsonTypeName:
#XmlRootElement(name = "login")
#JsonTypeName("login")
public class LoginApi implements IRestBean {
...
}
These are both Jackson-specific annotations.

Categories