This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 3 years ago.
I have a YAML File with following data and i want to Deserialize that into java Object.
connector:
provider: ABCD
nodes:
- 1.1.2.1
- 1.2.2.8
maxconnection: 1
minconnection: 1
1.First i created a class called Connector where i have defined all the variable like Provider, Nodes, maxconnections, Minconnections.
2.Then i created a class called group and call the Connector class.
3.then in the main function tried to load the yaml file and call it.
But i got Null pointer exception Error
#Connector Class
public static class connector {
private String provider;
private String[] nodes;
private int max_connections;
private int min_connections;
public String getprovider() {
return provider;
}
public void setprovider(String provider) {
this.provider = provider;
}
public String[] getnodes() {
return nodes;
}
public void setnodes( String[] nodes) {
this.nodes = nodes;
}
public int getmax_connections() {
return max_connections;
}
public void setmax_connections(int max_connections) {
this.max_connections = max_connections;
}
public int getmin_connections() {
return min_connections;
}
public void setmin_connections(int min_connections) {
this.min_connections = min_connections;
}
#Override
public String toString() {
return "connector: {provider: " + this.provider + ", nodes: " + this.nodes + ",max_connections: " + this.max_connections +",min_connections: " + this.min_connections +"}";
}
#Group class
public static class Group {
private ArrayList<connector> connector;
public ArrayList<connector> getconnector() {
return connector;
}
public void setconnector(ArrayList<connector> connector) {
this.connector = connector;
}
}
#main Class
public static void main(String[] args) {
final URL resource = YamlConf.class.getResource("demo.yaml");
final Constructor peopleContructor = new Constructor(Group.class);
final TypeDescription peopleDescription = new TypeDescription(connector.class);
peopleDescription.putMapPropertyType("connector", connector.class, Object.class);
peopleContructor.addTypeDescription(peopleDescription);
final Yaml yaml = new Yaml(peopleContructor);
try {
final Group group = (Group) yaml.load(resource.openStream());
for (final connector connector : group.getconnector()) {
System.out.println(connector);
}
} catch (IOException e) {
e.printStackTrace();
}
}
As you are using Jackson, you could use ObjectMapper with the YAMLFactory. Under the hood, it will use the SnakeYAML library. For the required dependencies, refer to the jackson-dataformats-text project documentation.
You could define your classes as follows:
#Getter
#Setter
#NoArgsConstructor
public class Content {
private Connector connector;
}
#Getter
#Setter
#NoArgsConstructor
public class Connector {
private String provider;
private List<String> nodes;
#JsonProperty("maxconnection")
private Integer maxConnections;
#JsonProperty("minconnection")
private Integer minConnections;
}
The #Getter, #Setter and #NoArgsConstructor annotations are from the Lombok project. They will generate getters, setters and the default constructor for your. If you don't want to use Lombok, simply implement those methods.
Then read your YAML file:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
Content content = mapper.readValue(yamlFile, Content.class);
Depending on your needs, you could consider a Map<String, Object> to hold the values:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
TypeReference<Map<String, Object>> type = new TypeReference<Map<String, Object>>() {};
Map<String, Object> content = mapper.readValue(yamlFile, type);
Related
I have the following json
{
"root": {
"status": "UP",
"connection1": {
"status": "UP"
},
"connection2": {
"status": "UP"
}
}
}
Also i have the following POJO classes i want to convert JSON into
#JsonIgnoreProperties(ignoreUnknown = true)
public class POJO {
#JsonProperty("root")
#JsonDeserialize(using = RootDeserializer.class)
private Root root;
//getters + setters
}
public class Root {
private boolean isAlive;
private List<Connection> connections;
public Root(boolean isAlive, List<Connection> connections) {
this.isAlive = isAlive;
this.connections = connections;
}
//getters + setters
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Connection {
private String status;
//getters + setters
}
And finally i have this deserializer to convert json into Root instance
public class RootDeserializer extends JsonDeserializer<Root> {
private static final String CONNECTION_PREFIX = "connection";
private static final String UP_STATUS = "UP";
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public Root deserialize(JsonParser parser, DeserializationContext context) throws IOException {
Map<String, Map<String, Object>> rootJsonMap = parser.readValueAs(Map.class);
boolean isAlive = StringUtils.equals(UP_STATUS, String.valueOf(rootJsonMap.get("status")));
List<Connection> connections = rootJsonMap.entrySet()
.stream()
.filter(entry -> StringUtils.startsWithIgnoreCase(entry.getKey(), CONNECTION_PREFIX))
.map(this::mapToConnection)
.collect(Collectors.toList());
return new Root(isAlive, connections);
}
private PosServerConnection mapToConnection(Map.Entry<String, Map<String, Object>> entry) {
Map<String, Object> connectionJsonMap = entry.getValue();
return objectMapper.convertValue(connectionJsonMap, Connection.class);
}
}
This way i can group all my Connections into one List in Root class.
My question is there any another way to do this ??
I'd like to do this without such big deserializer using just Jackson annotations on my Pojo classes
You can simply achieve this by using #JsonAnySetter annotation for customizing Setter for List<Connection> as follows. You can also reference to Jackson Annotation Examples to see how it works.
POJOs
public class Pojo {
private Root root;
//general getters, setters and toString
}
public class Root {
private String status;
private List<Connection> connections = new ArrayList<>();
public List<Connection> getConnections() {
return connections;
}
#JsonAnySetter
public void setConnections(String name, Connection connection) {
connection.setName(name);
this.connections.add(connection);
}
//other getters, setters and toString
}
public class Connection {
private String name;
private String status;
//general getters, setters and toString
}
Then you can serialize the given JSON string to Pojo with common way by Jackson:
Code Snippet
ObjectMapper mapper = new ObjectMapper();
Pojo pojo = mapper.readValue(jsonStr, Pojo.class);
System.out.println(pojo.getRoot().getConnections().toString());
Console output
[Connection [name=connection1, status=UP], Connection [name=connection2, status=UP]]
application.properties file contains properties that have sub properties:
status.available=00, STATUS.ALLOWED
status.forbidden=01, STATUS.FORBIDDEN
status.authdenied=05, STATUS.AUTH_DENIED
The idea was to get those properties into the application like this:
#Configuration
#ConfigurationProperties(prefix = "status")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private Map <String, List <String>> statusMapping;
public Map <String, List <String>> getStatusMapping () {
return statusMapping;
}
public void setStatusMapping (Map <String, List <String>> statusMapping) {
this.statusMapping = statusMapping;
}
}
The problem is that this Map is returned empty. I must be doing something wrong. Maybe this is not even possible in Spring to do like this?
I'm not sure about your choice regarding the data type and its assignment. I'd suggest you to rethink this design.
To your main question:
Spring can't know, that status.* should be mapped to private Map <String, List <String>> statusMapping;. Also as your class is named *properties, It seems that you don't want it to be a #Configuration class. Consider the following pattern:
First, create a properties class to hold the properties:
#ConfigurationProperties(prefix = "status")
public class StatusProperties {
private Map.Entry<Integer, String> available;
private Map.Entry<Integer, String> forbidden;
private Map.Entry<Integer, String> authdenied;
public Map.Entry<Integer, String> getAvailable() {
return available;
}
public void setAvailable(Map.Entry<Integer, String> available) {
this.available = available;
}
public Map.Entry<Integer, String> getForbidden() {
return forbidden;
}
public void setForbidden(Map.Entry<Integer, String> forbidden) {
this.forbidden = forbidden;
}
public Map.Entry<Integer, String> getAuthdenied() {
return authdenied;
}
public void setAuthdenied(Map.Entry<Integer, String> authdenied) {
this.authdenied = authdenied;
}
}
Now, your IDE should be able to read the docs from the setters while editing application.properties and check the validity. Spring can autowire the fields and automatically create the correct data types for you.
Consider mapping the Entries to a Map (Or, as I already told, change the design)
Now, you can use this properties class in your configuration:
#Configuration
#EnableConfigurationProperties(StatusProperties.class)
public class StatusConfiguration {
#Bean
public MyBean myBean(StatusProperties properties) {
return new MyBean(properties);
}
}
I found the solution:
application.properties:
app.statuses[0].id=00
app.statuses[0].title=STATUS.ALLOWED
app.statuses[1].id=01
app.statuses[1].title=STATUS.FORBIDDEN
app.statuses[2].id=02
app.statuses[2].title=STATUS.CONTRACT_ENDED
Properties.java
#Component
#ConfigurationProperties(prefix = "app")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private List<Status> statuses = new ArrayList<>();
public List <Status> getStatuses () {
return statuses;
}
public void setStatuses (List <Status> statuses) {
this.statuses = statuses;
}
public static class Status {
private String id;
private String title;
public String getId () {
return id;
}
public void setId (String id) {
this.id = id;
}
public String getTitle () {
return title;
}
public void setTitle (String title) {
this.title = title;
}
}
}
Regarding Spring HATEOAS Resource Wrapper vs ResourceSupport extention, i have:
public class FolderDto<T> implements Serializable {
private Number id;
private String name;
private final List<FolderDto<T>> folders;
private final List<T> items;
...
}
public class BoardDto implements Serializable {
private String name;
private Integer id;
private boolean shared;
...
}
#Test
public void test() throws Exception {
FolderDto<Resource<BoardDto>> folder = new FolderDto<Resource<BoardDto>>(0, null);
FolderDto<Resource<BoardDto>> child = new FolderDto<>(1000, "root");
folder.getFolders().add(child);
child.getItems().add(new Resource<>(new BoardDto(333,"whiteBoard", true)));
Resource<FolderDto<Resource<BoardDto>>> resource = new Resource<>(folder, linkTo(methodOn(BoardController.class).getBoards(null)).withSelfRel());
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(System.out, resource);
}
The result will be:
{
"id":0,
"nodes":[
{
"id":1000,
"name":"root",
"leaves":[
{
"content":{
"id":333,
"name":"whiteBoard",
"shared":true
},
"links":[{"rel":"board","href":"http://localhost/internal/boards/333"}]
}
]
}
],
"links":[{"rel":"self","href":"http://localhost/internal/boards"}]
}
How can i have this without the content element wrapper using Resource ?
In order to split responsibilities the rest controller logic (hateoas) entities should be as clean as possible and not have dependencies to other libs.
Desire result (obtained with extends ResourceSupport) :
{
"id":0,
"nodes":[
{
"id":1000,
"name":"root",
"leaves":[
{
"id":333,
"name":"whiteBoard",
"shared":true,
"links":[{"rel":"board","href":"http://localhost/internal/boards/333"}]
}
]
}
],
"links":[{"rel":"self","href":"http://localhost/internal/boards"}]
}
In the below example, I have a primary class - A and its subclass - B. Both can be used as a property in the general class X.
public class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
}
public class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey, #JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
}
public class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
}
How can I use Jackson Polymorphic feature in order to correctly deserialize the below given json into their respective classes:
JSON A :
{ "keys" :{
"primary_key" : "abc"
}
}
JSON B :
{ "keys" : {
"primary_key" : "abc",
"secondary_key" : "xyz"
}
}
Expected Result: Map keys object to Class A for JSON A and Class B for JSON B.
Please suggest alternative suggestions too.
It feels like a pretty common problem and there is no easy annotations way to solve it (Or maybe i just cant find one):
Jackson Polymorphic Deserialization - Can you require the existence of a field instead of a specific value?
Deserializing polymorphic types with Jackson
One thing you can do is to add custom deserializer to your object mapper. Here is nice demo of this approach: https://stackoverflow.com/a/19464580/1032167
Here is demo related to your example:
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
public class Main4 {
private static final String jsonA = "{ \"keys\" : { \"primary_key\" : \"abc\" } }";
private static final String jsonB =
"{ \"keys\" : { \"primary_key\" : \"abc\", \"secondary_key\" : \"xyz\" } }";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref");
idAsRefModule.addDeserializer(A.class, new AJsonDeserializer());
mapper.registerModule(idAsRefModule);
X tl = mapper.readValue(jsonA, X.class);
System.out.println(tl);
X t2 = mapper.readValue(jsonB, X.class);
System.out.println(t2);
}
public static class AJsonDeserializer extends JsonDeserializer<A>{
#Override
public A deserialize(JsonParser jp, DeserializationContext dc)
throws IOException {
ObjectCodec codec = jp.getCodec();
JsonNode node = codec.readTree(jp);
if (node.has("secondary_key")) {
return codec.treeToValue(node, B.class);
}
return new A(node.findValue("primary_key").asText());
}
}
public static class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
#Override
public String toString() {
return "A{" +
"primaryKey='" + primaryKey + '\'' +
'}';
}
}
public static class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey,
#JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
#Override
public String toString() {
return "B{" +
"primaryKey='" + primaryKey + '\'' +
"secondaryKey='" + secondaryKey + '\'' +
'}';
}
}
public static class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
#Override
public String toString() {
return "X{" +
"keys=" + keys +
'}';
}
}
}
But you will have to create one more super class if you want to use default A deserializer or look here how you can solve this: https://stackoverflow.com/a/18405958/1032167
If I understoon correctly, simply passing the values will work, without any config. I believe this is what you are looking for:
public class Test {
private static final String JSON = "{\"keys\":{\"primary_key\":\"abc\",\"secondary_key\":\"xyz\"}}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
X x = mapper.readValue(JSON, X.class);
System.out.println(mapper.writeValueAsString(x));
}
}
class A {
private String primary_key;
public String getPrimary_key() {
return primary_key;
}
public void setPrimary_key(String primary_key) {
this.primary_key = primary_key;
}
}
class B extends A {
private String secondary_key;
public String getSecondary_key() {
return secondary_key;
}
public void setSecondary_key(String secondary_key) {
this.secondary_key = secondary_key;
}
}
class X {
private B keys;
public B getKeys() {
return keys;
}
public void setKeys(B keys) {
this.keys = keys;
}
}
Output will be:
{"keys":{"primary_key":"abc","secondary_key":"xyz"}}
In case this is not what you expect, please provide another explanation and I will edit the answer as needed.
Disclaimer: I'm new to all this, so my terminology may be wrong
I've got some Java POJO's I want to serialize to JSON & XML. I'm using MOXy 2.5.0 for JSON and Jersey 2.4.1.
#XmlRootElement
class Root {
// #XmlElements({#XmlElement(name = "destination_address", type = LatLong.class),
// #XmlElement(name = "destination_address", type = Polygon.class)})
public Object[] destination_addresses;
}
public class LatLong {
public double lat, lng;
}
public class Polygon {
protected List<LatLong> points = new ArrayList<LatLong>();
#XmlElements({#XmlElement(name = "lat", type = Lat.class),
#XmlElement(name = "lng", type = Lng.class)})
private LatOrLong[] getLatOrLongs() {
LatOrLong[] retval = new LatOrLong[points.size() * 2];
for (int point = 0; point < points.size(); point++) {
LatLong latLong = points.get(point);
retval[point * 2] = new Lat(latLong.lat);
retval[point * 2 + 1] = new Lng(latLong.lng);
}
return retval;
}
static abstract private class LatOrLong {
#XmlValue
private double latOrLong;
private LatOrLong() {}
private LatOrLong(double latOrLong) {this.latOrLong = latOrLong;}
}
static private class Lat extends LatOrLong {
private Lat() {}
private Lat(double lat) {super(lat);}
}
static private class Lng extends LatOrLong {
private Lng() {}
private Lng(double lng) {super(lng);}
}
}
This doesn't work in XML with the two lines commented out, but in JSON, MOXy is adding a type: latLong attribute to the destination_addresses array, as well as using the toString() method of Polygon.
How can I hide the type?
How can I get MOXy to use getLatOrLongs() instead of toString()?
EDIT: I've simplified Polygon to just serialize points and changed destination_addresses to be a List<Object> instead of Object[] .
The pojo mapping feature is by default enabled using MOXy.
But if for any case, you need to implement a specific marshal/unmarshal (I take here ObjectID from MongoDB which is a t:
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.bson.types.ObjectId;
public class ObjectIdXmlAdapter extends XmlAdapter<String, ObjectId> {
#Override
public String marshal(ObjectId id) throws Exception {
if(id == null) {
return null;
} else {
return id.toString();
}
}
#Override
public ObjectId unmarshal(String id) throws Exception {
return new ObjectId(id);
}
}
And then, on your POJO:
#XmlJavaTypeAdapter(ObjectIdXmlAdapter.class)
public ObjectId getId() {
return id;
}
Will serialize your id element as expected...
Hope this helps, this should be the main trouble.