JAVA ObjectNode get is empty - java

My JSON in my #RequestBody ObjectNode objectNode
{
"script":
{"id":2,"nom":"tes","libelleprerequiss":
[{"id":1,"libelle_prerequis":"Soc devis VAM","produit":{"id":1,"nom":"VAM"},"typologie":{"id":1,"nom":"devis"}}]
},
"libellePrerequis":
{"id":3,"libelle_prerequis":"Soc contrat VAM","produit":{"id":1,"nom":"VAM"},"typologie":{"id":2,"nom":"contrat"}
}
}
When i do :
String id_script = objectNode.get("script").get("id").asText();
String id_libelleprerequis = objectNode.get("libellePrerequis").get("id").asText();
System.out.println("Script ID = " + id_script + "...");
System.out.println("Libelle Prerequis ID = " + id_libelleprerequis + "...");
i Have a result :
Update Script - Script ID = 2...
Update Script - Libelle Prerequis ID = 3...
But when i do :
String libelleprerequis = objectNode.get("libellePrerequis").asText();
System.out.println("Update Script - Libelle Prerequis = " + libelleprerequis + "...");
I have :
Update Script - Libelle Prerequis = ...
I would like to recover my "LibellePrerequis" to integrate it to my script in my entity (many to many)

"libellePrerequis" is an object.
asText() works only if your node is a value.
You can check with objectNode.get("libellePrerequis").isValueNode();
To get a node object as String, you need to use toString():
objectNode.get("libellePrerequis").toString();
EDIT1:
To transform your node into object you need to create some objects which represent the tree of your node.
public class Libell{
private int id;
private String libelle_prerequis;
private Produit produit;
private Typologie typologie;
//getters, setters
}
public class Typologie{
private int id;
private String nom;
//getters, setters
}
public class Produit{
private int id;
private String nom;
//getters, setters
}
The most important thing is to have the same name for attributes(id, libelle_prerequis, ...) as in json and pay attention to their type. So the name of objects (Libell, Typologie and Produit) doesn't matter if their structure is like in json.
Now, to map your JSonNode to an object you can do this:
ObjectMapper objMap = new ObjectMapper();
Libell libell = objMap.convertValue(objectNode.get("libellePrerequis"), Libell.class);
System.out.println(libell.getId());
System.out.println(libell.getProduit().getId());
System.out.println(libell.getTypologie().getId());

Related

What's the ideal way of combining two Object Arrays in Java

Class A:
public class A{
private String Id;
private List<Product> products = new ArrayList<>();
}
public class Product{
private String prodId;
private String color;
private String size;
}
Example:
A = { "Id": 1 , products: [{"prodId":"Prod1", "color":"Red", "size":"L"},
{"prodId":"Prod2", "color":"Green", "size":"L"}] }
Class B:
public class B{
private String Id;
private List<ProductAvail> productAvail = new ArrayList<>();
}
public class ProductAvail{
private String prodId;
private String availability;
private String region;
}
Example:
B = { "Id": 1 , productAvail: [{"prodId":"Prod1", "availability":"Y", "region":"AA"},
{"prodId":"Prod2", "availability":"N", "region":"BB"}] }
I want to combine them into one like
public class C{
private String Id;
private List<Catalog> catalog = new ArrayList<>();
}
public class Catalog{
private String prodId;
private String color;
private String size;
private String availability;
private String region;
}
C = { "Id": 1 , catalog: [{"prodId":"Prod1", "color":"Red", "size":"L", "availability":"Y", "region":"AA"},
{"prodId":"Prod2", "color":"Green", "size":"L", "availability":"N", "region":"BB"}] }
What's the ideal way to do this in Java without using two for loops?
Given:
String aJson = "{ \"id\": 1 ,\"products\": [{\"prodId\":\"Prod1\", \"color\":\"Red\", \"size\":\"L\"}, {\"prodId\":\"Prod2\", \"color\":\"Green\", \"size\":\"L\"}] }";
String bJson = "{ \"id\": 1 ,\"productAvail\": [{\"prodId\":\"Prod1\", \"availability\":\"Y\", \"region\":\"AA\"}, {\"prodId\":\"Prod2\", \"availability\":\"N\", \"region\":\"BB\"}] }";
You can do:
ObjectMapper objectMapper = new ObjectMapper();
A a = objectMapper.readValue(aJson, A.class);
B b = objectMapper.readValue(bJson, B.class);
Map<String, ProductAvail> idToProductAvail = b.getProductAvail().stream()
.collect(Collectors.toMap(ProductAvail::getProdId, Function.identity()));
List<Catalog> catalogs = a.getProducts().stream()
.map(p -> {
ProductAvail pa = idToProductAvail.get(p.getProdId());
return new Catalog(pa.getProdId(), p.getColor(), p.getSize(),
pa.getAvailability(), pa.getRegion());
}).collect(Collectors.toList());
C c = new C(a.getId(), catalogs);
System.out.println(objectMapper.writeValueAsString(c));
Output:
{"id":"1","catalog":[{"prodId":"Prod1","color":"Red","size":"L","availability":"Y","region":"AA"},{"prodId":"Prod2","color":"Green","size":"L","availability":"N","region":"BB"}]}
Assuming you want to do this action in Java, after parsing your source data, you have two choices.
Guarantee that your source data is ordered by prodId for both lists, and that every Product has a corresponding ProductAvail. You can then simply assume that the index of the first has a valid entry at the same index in the second and traverse them both with a single loop.
Change your source data and class members such that you have Maps instead of Lists as output, using prodId as the key for each entry. You can then iterate the keys from the map of Products and use them to access both the Product and ProductAvail maps in a single loop.
-
If you can't make the guarantees from (1) or the changes from (2), you're stuck with your worst case of foreach product { foreach productavail { ... } } because you have to discover the correct ProductAvail for each Product.
You could take steps mitigate the impact by nulling discovered inner list members and skipping nulls, or by using parallelStream to throw the inner loop for each outer iteration at a different thread, but no action at this point avoids needing some riff on loop{loop{}} - as a best case.

Jackson xml deserialization - serialize to a list with arbitrary elements in between

I am using Jackson to deserialize XML. It works fine when serializing into a list when there are no other elements in between, but if i insert some other field in the middle somewhere, it seems to only put the things below the field into my list. See below only the ProductA objects after the Product B tag are being included.
I set a breakpoint to get the JsonNode object using the xmlMapper readTree method, and the properties above the ProductB tag are not there in the node tree.
Is there a way using jackson (or another library) to get all ProductA elements inside the Warehouse element, independent of the ordering of the Warehouse child elements?
public class Warehouse {
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName = "ProductA")
private List<ProductA> productAList;
#JacksonXmlProperty(localName = "ProductB")
private String productB;
//getters and setters
}
public class ProductA {
#JacksonXmlProperty(localName = "PropA")
private String propA;
//getters and setters
}
<Warehouse>
<ProductA>
<propA>abc</propA>
</ProductA>
<ProductA>
<propA>abc</propA>
</ProductA>
<ProductB>def</ProductB>
<ProductA>
<propA>abc</propA>
</ProductA>
</Warehouse>
Change your Warehouse class to use a method just for setting the value of productAList from XML, and remove the annotations from the productAList property.
#JsonSetter(value = "ProductA")
public void setProductAListFromXml(ProductA productA) {
if (this.productAList == null) {
this.productAList = new ArrayList<ProductA>();
}
this.productAList.add(productA);
}
Here is the full Warehouse class:
#JacksonXmlRootElement(localName = "Warehouse")
public class Warehouse {
private List<ProductA> productAList;
#JacksonXmlProperty(localName = "ProductB")
private String productB;
public List<ProductA> getProductAList() {
return productAList;
}
public void setProductAList(List<ProductA> productAList) {
this.productAList = productAList;
}
#JsonSetter(value = "ProductA")
public void setProductAListFromXml(ProductA productA) {
if (this.productAList == null) {
this.productAList = new ArrayList<ProductA>();
}
this.productAList.add(productA);
}
public String getProductB() {
return productB;
}
public List<ProductA> getProductA() {
return productAList;
}
}
Using the class like this:
String str = "<Warehouse>\r\n" +
" <ProductA>\r\n" +
" <propA>abc</propA>\r\n" +
" </ProductA>\r\n" +
" <ProductA>\r\n" +
" <propA>abc</propA>\r\n" +
" </ProductA>\r\n" +
" <ProductB>def</ProductB>\r\n" +
" <ProductA>\r\n" +
" <propA>abc</propA>\r\n" +
" </ProductA>\r\n" +
"</Warehouse>";
XmlMapper mapper = new XmlMapper();
Warehouse warehouse = mapper.readValue(str, Warehouse.class);
System.out.println(warehouse.getProductB());
System.out.println(warehouse.getProductA());
Produces this output:
def
[ProductA [propA=abc], ProductA [propA=abc], ProductA [propA=abc]]

avoid repetitive code Java

I am working on ETL Java project and it does 3 things
extract - read the data from a table
transform the data to JSON
Load the data
It works fine. The issue is I am doing it for each table. The way I have right now is
class ETLHelper
{
private Person read(ResultSet results){
Person p = new Person();
p.setPersonId(results.getString("PERSON_ID"));
p.setPersonName(results.getString("PERSON_NAME"));
return p;
}
private String transform(Person p){
TransformPerson t = new TransformPerson();
t.setTransformPersonId(p.getPersonId);
t.setTransformPersonName(p.getPersonName);
PersonEData eData = new PersonEData();
eData.setDate1(p.date1);
eData.setDate2(p.date2);
t.seteData(eData);
PersonDetails pd = new PersonDetails();
pd.settransformdata(t);
return writeValueAsString(pd);
}
public void etl(){
Connection c = null;
PreparedStatement p = null;
ResultSet r = null;
c = getConnection();
p = c.prepareStatement(getSql());
r = p.executeQuery();
while(r.next()){
messages.add(transform(read(r)));
/*code for loading data*/
}
}
}
Person.Java:
#JsonTypeName(value = "PERSON")
#JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
public class Person{
#JsonProperty(value = "PERSON_ID")
private String personId;
//getter and setter for personId
#JsonProperty(value = "PERSON_NAME")
private String personName;
//getter and setter for personName
}
TransformPerson.java:
#JsonRootName(value = "Person")
class TransformPerson{
private String transformPersonName;
private String transformPersonId;
/*getter and setter for transformPersonName and tranformPersonId*/
#override
String toString(){
return "Person [name =" + transformPersonName + ", id = " + transformPeronId "]";
}
}
PersonEdata:
private String date1;
private String date2;
/*getter and setter*/
#override
public String toString(){
return "PersonEdata [date1=" + date1 +", date2=" + date2 + "]";
}
So a Person class, a class needed for transformation and etl class is written for each table. There are also some additional classes like PersonEdata that returns JSON when toString() is called. Is there anyway can I change this design to avoid writing the similar code for each table? There are some constraints. Each table is different and they transformation class is needed because there are other programs that uses the JSON generated so we need to generate JSON that needs to understood by those programs.
In your current solution, you have created :
Person class - to hold the data retrieved from DB
PersonTransform class - to copy the data from Person to other representation and have extended the capability to create JSON by overrinding toString()
To keep it simple what you can do is:
Have single Class like Person for each entity (Table) - which is JSON Serializable.
Don't override the toString method to represent the JSON representation - use JSON serializer instead.

Object mapping fields

I have an objects class A:
public class A {
private Long id;
private String name;
private String mail;
private String moreData;
// ...
}
class B:
public class B {
private Long id;
private String name;
private String crc;
// ...
}
Can I use jackson to provide field mapping from object A to B copying correspond fields into target object.
I need from object
A {
Long id = 23L;
String name = "name";
String mail = "mail";
String moreData = "moreData";
// ...
}
get as target object
B {
Long id = 23L;
String name = "name";
String crc = mull;
// ...
}
after object mapping processing...
Is it possible implement solution using com.fasterxml.jackson in simple way?
Sure you can. Not withstanding a full understanding of why you want to do this, or that I think there might be more efficient ways than converting to JSON then back, but if you would like to use Jackson, here is what I would do:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
B b = objectMapper.readValue(objectMapper.writeValueAsString(a), B.class);
Hope this helps. should do the job. The key will be to tell Jackson to not fail on unknown properties so it drops those you are not sure of.

SimpleFramwork XML: Element with Inner Text and Child Elements

I have the following situtation in deserializing xml using SimpleFramework of specific format that cannot be changed...
<Question ID="Q1">
THIS INNER TEXT IS THE ISSUE
<Criteria Type="Normal" Source="OEM">
<Value Type="0">45.7</Value>
<Value Type="100">42.7</Value>
</Criteria>
<Criteria Type="Impact" Source="OEM">
<Value Type="0">45.7</Value>
<Value Type="100">42.7</Value>
</Criteria>
<!-- CRITERIA CAN HAVE ANY NUMBER -->
</Question>
and here is the class I wrote for Question
#Root (name="Question")
public class Question {
#Attribute (name="ID")
private String id;
#ElementList (inline=true, required=false)
private List<Criteria> criteria;
#Text
private String text;
// And their getter and setters...
}
Now the issue is that, I CANNOT GET INNER TEXT...
Can anybody suggest me the way to do this...???
You can't use #Text annotation here, this is only possible if you don't have any child.
and it [#Text annotation] can not appear with the another XML element annotations, such
as the Element annotation.
Source: #Text API documentation
However, you can use a Converter to those text. This is a bit tricky, but here's an example:
Criteria class:
#Root(name = "Criteria")
public class Criteria
{
#Attribute(name = "Type")
private String type;
#Attribute(name = "Source")
private String source;
#ElementList(name = "Values", inline = true)
private ArrayList<Value> values;
public Criteria(String type, String source)
{
this.type = type;
this.source = source;
this.values = new ArrayList<>();
}
private Criteria() { }
// ...
#Override
public String toString()
{
return "Criteria{" + "type=" + type + ", source=" + source + ", values=" + values + '}';
}
// Inner class for values - you also can use a normal one instead
#Root(name = "Value")
public static class Value
{
#Attribute(name = "Type", required = true)
private int type;
#Text(required = true)
private double value;
public Value(int type, double value)
{
this.type = type;
this.value = value;
}
private Value() { }
}
}
Question class:
#Root(name = "Question")
#Convert( value = Question.QuestionConvert.class)
public class Question
{
#Attribute(name = "ID", required = true)
private String id;
#Element(name = "text")
private String text;
#ElementList(inline = true)
private ArrayList<Criteria> criteria;
public Question(String id)
{
this.id = id;
this.criteria = new ArrayList<>();
this.text = "This inner text ...";
}
private Question() { }
// ...
#Override
public String toString()
{
return "Question{" + "id=" + id + ", text=" + text + ", criteria=" + criteria + '}';
}
static class QuestionConvert implements Converter<Question>
{
private final Serializer ser = new Persister();
#Override
public Question read(InputNode node) throws Exception
{
Question q = new Question();
q.id = node.getAttribute("ID").getValue();
q.text = node.getValue();
q.criteria = new ArrayList<>();
InputNode criteria = node.getNext("Criteria");
while( criteria != null )
{
q.criteria.add(ser.read(Criteria.class, criteria));
criteria = node.getNext("Criteria");
}
return q;
}
#Override
public void write(OutputNode node, Question value) throws Exception
{
node.setAttribute("ID", value.id);
node.setValue(value.text);
for( Criteria c : value.getCriteria() )
{
ser.write(c, node);
}
}
}
}
Please note all those empty constructors. They are required by simple but you can keep them private. You don't have to implement those inner classes as inner.
The key to the solution is the Converter which allows you to use text and child-elements together. You can use a Serializer to write all the Criteria-childs.
There are some toString() methods, those are only for testing - you can implement them as you need.
Input XML:
<Question ID="Q1">This inner text ...
<Criteria Type="Normal" Source="OEM">
<Value Type="0">45.7</Value>
<Value Type="100">42.7</Value>
</Criteria>
<Criteria Type="Impact" Source="OEM">
<Value Type="0">45.7</Value>
<Value Type="100">42.7</Value>
</Criteria>
</Question>
Example code:
Serializer ser = new Persister(new AnnotationStrategy()); // Don't miss the AnnotationStrategy!
Question q = ser.read(Question.class, f);
System.out.println(q);
Output:
Question{id=Q1, text=This inner text ...
, criteria=[Criteria{type=Normal, source=OEM, values=[Value{type=0, value=45.7}, Value{type=100, value=42.7}]}, Criteria{type=Impact, source=OEM, values=[Value{type=0, value=45.7}, Value{type=100, value=42.7}]}]}
Not very beautiful, but it's working! :-)
Ps. Since both methods of the Converter are implemented you also can use this code to serialize a Question object.

Categories