I want to serialize an List of Object with SimpleFramework xml.
I succeed with ordinary class but not with List of object.
I don't find the good syntax for do it with a List of object.
List< Shop > shop = new Persister().read(List<Shop>.class, data);
List< Shop >.class doesn't work
Thanks
It's not possible to do this directly; use #ElementList instead.
Here's an example:
Shop class
#Default // Or customize as you need
public class Shop
{
private String name;
public Shop(String name)
{
this.name = name;
}
private Shop() { /* Required default ctor */ }
// ...
}
ListExample
This is just a wrapper around the list.
#Root(name = "example")
public static class ListExample
{
#ElementList(name = "Shops", inline = true)
private List<Shop> shops;
// ...
}
Usage
String input = ... // Or what source you have
Serializer ser = new Persister();
ListExample readExample = ser.read(ListExample.class, input);
Related
I'm using Java library ModelMapper to map my local (JPA) model to a DTO version.
Model
#Entity
public class City {
#Id #GeneratedValue
private Long id;
private String postalCode;
private String name;
// getter/setter/etc.
}
DTO
public class CityDto {
private Long id;
private String postalCode;
private String name;
// getter/setter/etc.
}
Wrapper
I'm going to pass lists of these classes with an additional int, for which I created a genericly typed wrapper class.
public class Wrapper<T> {
private List<T> entities;
private Integer count;
//getter/setter/etc.
}
Question
Now, I want to convert a Wrapper<City> into a Wrapper<CityDto>. I thought that it's the same as converting a List<City> into List<CityDto> as explained in the answer to ModelMapper changes generic type on runtime - weird behavior.
#Test
public void test() {
// prepare
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
Wrapper<City> wrapper = ...
// act
Type type = new TypeToken<Wrapper<CityDto>>() {}.getType();
Wrapper<CityDto> cityDtos = modelMapper.map(wrapper, type);
// check
Assertions.assertTrue(cityDtos.getEntities().get(0) instanceof CityDto);
}
This test fails. I get the same exception as in the aforementioned question
java.lang.ClassCastException: com.my.model.City cannot be cast to com.my.dto.CityDto
The nested list has objects of the type City and not the expected CityDto. What am I doing wrong?
Remark
As a workaround I am doing something like this.
Wrapper<City> wrapper = ...
List<CityDto> cityDtos = modelMapper.map(wrapper.getEntities(), new TypeToken<List<CityDto>>() {}.getType());
Wrapper<CityDto> converted = new Wrapper<>(cityDtos, wrapper.getCount());
That works, but I'd like to know why my original idea is not working.
I have the following DTOs:
public class ConsumerDTO {
private String amount;
//...some other fields
}
public class ReceiverDTO {
private Set<PriceInfoDto> prices;
//...some other fields
}
public class PriceInfoDto {
private String amount;
//...some other fields
}
I want to convert ConsumerDTO to ReceiverDTO, p.s. map my data between differently structured objects. ConsumerDTO is my source class. ReceiverDTO is my target class. I tried this:
TypeMap<ConsumerDTO , ReceiverDTO> propertyMapper = this.mapper.createTypeMap(ConsumerDTO .class, ReceiverDTO.class);
propertyMapper.addMapping(ConsumerDTO ::getAmount, ReceiverDTO::getAmount);
But having trouble with getting amount from set in my target class. Is there a way to solve this? I also read some articles, but they show examples with simple types.
You can achieve the target object of ReceiverDTO from your source object ConsumerDTO as below:
Approach Here:
Here, I have added few more sample fields in the source as well as target class to show how to map other fields while creating a target object of type ReceiverDTO using getTargetTypeObject method.
public class Test {
public static void main(String[] args) {
//Source class objects
ConsumerDTO sourceObj = new ConsumerDTO();
sourceObj.setAmount("100");
//sample for Other fields in source object
sourceObj.setName("test");
sourceObj.setId(1000);
sourceObj.setFlag(true);
//sample for Other fields in source object
ReceiverDTO targetType = getTargetTypeObject(sourceObj);
System.out.println(targetType);
}
private static ReceiverDTO getTargetTypeObject(ConsumerDTO x) {
//Intermediate object creations
PriceInfoDto dto = new PriceInfoDto();
dto.setAmount(x.getAmount());
//Set other fields like this
dto.setName(x.getName());
dto.setId(x.getId());
dto.setFlag(x.isFlag());
//Set other fields like this
Set<PriceInfoDto> set = new HashSet<>();
set.add(dto);
//Target object
ReceiverDTO receiverDTO = new ReceiverDTO();
receiverDTO.setPrices(set);
return receiverDTO;
}
}
class ConsumerDTO {
private String amount;
private String name;
private int id;
private boolean flag;
//getters and setters
//toString
}
class ReceiverDTO {
private Set<PriceInfoDto> prices;
//getters and setters
//toString
}
class PriceInfoDto {
private String amount;
private String name;
private int id;
private boolean flag;
//getters and setters
//toString
}
Output:
ReceiverDTO{prices=[PriceInfoDto{amount='100', name='test', id=1000, flag=true}]}
I have the following entity class:
public class Conversation {
private String id;
private String ownerId;
private Long creationDate;
public Conversation(String id, String ownerId, Long creationDate){
this.id = id;
this.ownerId = ownerId;
this.creationDate = creationDate;
}
}
On other submodule through an external service, on each insertion, I recive a map of the following entities:
public class AttributeValue {
private Sring s; //string attribute
private String n; //number attribute
public String getS() {
return this.s;
}
public String getN() {
return this.n;
}
public AttributeValue(String s, String n){
this.s = s;
this.n = n;
}
}
//Example if I insert this conversation: new Conversation("1", "2", 1623221757971)
// I recive this map:
Map<String, AttributeValue> insertStream = Map.ofEntries(
entry("id", new AttributeValue("1", null)),
entry("ownerId", new AttributeValue("2", null)),
entry("creationDate", new AttributeValue(null, "1623221757971"))
);
To read the ownerId field from the map, I have to do this:
String ownerId = insertStream.get("ownerId").getS();
My question is, instead of have to write: insertStream.get("ownerId"), exists any way through Reflection to read the name of the field from the entity (Conversation.ownerId)?
This is because we want to mantain the submodule and If we make a change on the entitity, for example change ownerId for ownerIdentifier, the submodule shows a compilation error or is changed automatically.
Is this what you want? Field#getName()
Example code:
Field[] conversationFields = Conversation.class.getDeclaredFields();
String field0Name = conversationFields[0].getName();
Depending on the JVM used, field0Name can be "id". You can also use Class#getFields(), this method includes all Fields that are accessible in this class (super class's fields).
Another option (not using reflection) would be to refactor your code.
import java.util.Map;
import java.util.HashMap;
public class Conversation {
public static String[] names = {
"id", "ownerId", "creationDate"
};
private Map<String, Object> data = new HashMap<String,Object>();
public Conversation(Object... data) {
if(data.length!=names.length)
throw new IllegalArgumentException("You need to pass "+names.length+" arguments!");
for(int i=0; i<names.length; i++)
data.put(names[i],data[i]);
}
public Map<String,Object> getData() { return data; }
// You can pass "id"/"ownerId" or names[0]/names[1]
public String getString(String key) {
return (String)data.get(key);
}
// You can pass "creationDate" or names[2]
public long getLong(String key) {
return (long)data.get(key);
}
}
You could then create Conversation Objects like before:
Conversation c = new Conversation("myId","myOwnerId",123456789L);
You could also add public static String fields like ID="id", but changing the value of a field will never change the field's name.
I'm trying to create POJOs for the following JSON structure. The Fields node is easy enough to wire up, but I'm unsure how to use annotations to wire up the Description node. If I had been defining the JSON structure for that node, I'd have create an JsonArray of JsonObjects, which would make the java class easy, but since I didn't, I need to figure out how to serialize the structure below:
{
"Fields": {
"Required": ["ftp.hostname"],
"Optional": ["ftp.rootDirectory"]
},
"Description": {
"ftp.hostname": {
"label": "SFTP Hostname",
"description": "SFTP server hostname or IP address"
},
"ftp.rootDirectory": {
"label": "Root Directory",
"description": "The root path on the Data Store accessible by this connector"
}
}
}
Note that the nodes in the Description object have names that correlate to the values defined in the Fields node, which means their node names can vary from payload to payload.
The class for the Fields node:
public class FieldDetails {
public static final String REQUIRED = "Required";
public static final String OPTIONAL = "Optional";
#JsonProperty(value = REQUIRED, required = true)
private List<String> required;
#JsonProperty(value = OPTIONAL, required = true)
private List<String> optional;
}
And what I have so far for the entire object:
public class FieldDefinitions {
public static final String FIELDS = "Fields";
public static final String DESCRIPTION = "Description";
#JsonProperty(value = FIELDS, required = true)
private FieldDetails fields;
#JsonProperty(value = DESCRIPTION , required = true)
private ??? descriptions;
}
Generally, you can always map any JSON object to Map<String, Object>. If JSON is complicated with many nested objects, Jackson will automatically pick correct type: Map for objects and List for arrays.
You can also declare class like below for Description properties.
class Description {
private String label;
private String description;
// getters, setters, toString
}
The whole Description is a big JSON which you can map to Map<String, Description>. So, it could look like below:
class FieldDefinitions {
public static final String FIELDS = "Fields";
public static final String DESCRIPTION = "Description";
#JsonProperty(value = FIELDS, required = true)
private FieldDetails fields;
#JsonProperty(value = DESCRIPTION, required = true)
private Map<String, Description> descriptions;
// getters, setters, toString
}
Rest is the same. Example app:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.List;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
File json = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
FieldDefinitions fields = mapper.readValue(json, FieldDefinitions.class);
System.out.println("Required");
fields.getFields().getRequired().forEach(r ->
System.out.println(r + " = " + fields.getDescriptions().get(r)));
System.out.println("Optional");
fields.getFields().getOptional().forEach(r ->
System.out.println(r + " = " + fields.getDescriptions().get(r)));
}
}
For given JSON payload prints:
Required
ftp.hostname = Description{label='SFTP Hostname', description='SFTP server hostname or IP address'}
Optional
ftp.rootDirectory = Description{label='Root Directory', description='The root path on the Data Store accessible by this connector'}
That's the structure.
public class FieldDefinitions {
#JsonProperty("Fields")
public FieldDetails fields = new FieldDetails();
#JsonProperty("Description")
public Map<String, Property> properties = new HashMap<>();
}
public class FieldDetails {
#JsonProperty("Required")
public List<String> required = new ArrayList<>();
#JsonProperty("Optional")
public List<String> optional = new ArrayList<>();
}
public class Property {
public String label;
public String description;
}
What design-pattern, if any, would be most appropriate in this situation.
public class PersonFromDB1 {
private String firstName;
private String lastName;
private String Car;
}
public class PersonFromDB2 {
private String first_name;
private String last_name;
private String boat;
}
Out of these two person types, the only data I would like to work on is fist name and last name regardless of how it field name is name inside the different DBs. firstName and first_name represents the same - name of a person/customer - so does lastName and last-name. The car and boat fields are, in my example, completely irrelevant and should therefore be ignored.
Using, maybe polymorphism or the adapter pattern (?), I would like to create a list of objects that includes persons from DB1 and DB2 under the same type - of PersonInOurDB.
In the end, my goal is to be able to call GSON serialization/desarialization on myClass alone.
public class PersonInOurDB {
private String firstname;
private String lastname;
}
A simple selection based on the type is all you really need. This could be considered a builder pattern because it just initializes a new instance of myClass.
Note, this is rough pseudo code.
FunctionName(SomeType instance)
{
string aPostfix = "_1";
string bPostfix = "_2";
string selectedPostFix;
// This is your strategy selector
switch(typeof(SomeType.Name)
{
case "TypeA":
selectedPostFix = aPostFix;
case "TypeB":
selectedPostFix = bPostFix;
}
return new myClass()
{
A = instance.GetProperty("A" + selectedPostfix).Value,
B = instance.GetProperty("B" + selectedPostfix).Value,
...
}
}
If you want a common access api in java for both objects, then introduce an interface and let both implement it.
If you only want both objects (PersonFromDB1 and PersonFromDB2) to be serialized in the same way by json you can either:
use annotations - the #SerializedName annotation in combination with #Expose.
use the FieldNamingStratgy and ExclusionStrategy
Use annotations to control the serialization
public class PersonFromDB1 {
#Expose
#SerializedName("firstName")
private String firstName;
#Expose
#SerializedName("lastName")
private String lastName;
private String car;
}
public class PersonFromDB2 {
#Expose
#SerializedName("firstName")
private String first_Name;
#Expose
#SerializedName("lastName")
private String last_Name;
private String boat;
}
Then you can use the GsonBuilder
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
PersonFromDB1 person1 = ...; // get the object
PersonFromDB2 person2 = ...; // get the object
System.out.println(gson.toJson(person1));
System.out.println(gson.toJson(person2));
Use FieldNamingStratgy and ExclusionStrategy to control the serialization
If you don't want to modify the db objects (you can't or you don't want to add annotations) than there is another way. You can use a FieldNamingStratgy and ExclusionStrategy.
class PersonFromDBNamingStrategy implements FieldNamingStrategy {
Map<String, String> fieldMapping = new HashMap<String, String>();
public PersonFromDBNamingStrategy() {
fieldMapping.put("first_Name", "firstName");
fieldMapping.put("last_Name", "lastName");
}
#Override
public String translateName(Field f) {
String name = f.getName();
if(fieldMapping.contains(name)){
return fieldMapping.get(name);
}
return name;
}
}
and the ExclusionStrategy
class PersonFromDExclusionStrategy implements ExclusionStrategy {
List<String> validNames = Arrays.asList("car", "boat");
#Override
public boolean shouldSkipField(FieldAttributes f) {
String name = f.getName();
return !validNames.contains(name);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
after that just create Gson like this:
GsonBuilder gsonBuilder = new GsonBuilder();
sonBuilder.addSerializationExclusionStrategy(new PersonFromDExclusionStrategy());
gsonBuilder.setFieldNamingStrategy(new PersonFromDBNamingStrategy());
Gson gson = gsonBuilder.create();
PersonFromDB1 person1 = ...; // get the object
PersonFromDB2 person2 = ...; // get the object
System.out.println(gson.toJson(person1));
System.out.println(gson.toJson(person2));