Design-pattern to use to access similar objects with different field names - java

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));

Related

How to get the name of an Attribute from an Entity

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.

Map String to Object using Jackson with inheritance

I have the QueueContent class that it has is a superclass of two others.
I get a String in JSON format that contains the information I need to extract. The super class is:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class QueueContent {
private String empresa;
private String empresa_cor;
private String empresa_contato;
private String empresa_url;
private String empresa_telefone;
private String empresa_idioma;
public QueueContent(String empresa, String empresa_cor, String empresa_contato, String empresa_url, String empresa_telefone, String empresa_idioma) {
this.empresa = empresa;
this.empresa_cor = empresa_cor;
this.empresa_contato = empresa_contato;
this.empresa_url = empresa_url;
this.empresa_telefone = empresa_telefone;
this.empresa_idioma = empresa_idioma;
}
public QueueContent() {
}
}
I'm using Lombok to generate Getters / Setters)
This is the child class:
#Data
public class EmailCameraOffline extends QueueContent {
private Timestamp camera_last_online;
private String camera_nome;
private String empresa_url_plataforma;
public EmailCameraOffline(String empresa, String empresa_cor, String empresa_contato, String empresa_url, String empresa_telefone, String empresa_idioma, Timestamp camera_last_online, String camera_nome, String empresa_url_plataforma) {
super(empresa, empresa_cor, empresa_contato, empresa_url, empresa_telefone, empresa_idioma);
this.camera_last_online = camera_last_online;
this.camera_nome = camera_nome;
this.empresa_url_plataforma = empresa_url_plataforma;
}
public EmailCameraOffline() {
}
}
So I've done:
EmailCameraOffline infosEmail = new ObjectMapper().readValue(content, EmailCameraOffline.class);
System.out.println(infosEmail);
And the output is:
EmailCameraOffline (camera_last_online = 2020-03-12 03: 01: 45.0, camera_nome = Pier Cam 1, empresa_url_platform = null)
How do I get my EmailCameraOffline object to have the superclass attributes initialized?
Everything should be loaded and initialized just fine, so calling:
System.out.println(infosEmail.getEmpresa());
should give expected value.
Problem
The problem is in the default implementation of toString() method (done via #Data) at EmailCameraOffline class, which does not include inherited fields.
Solution
To fix this you can "override" #Data's toString() implementation to include inherited fields as well using Lombok as:
#Data
#ToString(callSuper = true)
public class EmailCameraOffline extends QueueContent {
...
}

Gson is not ignoring said #Expose annotations [duplicate]

This question already has answers here:
Serialize object using GSON
(2 answers)
Closed 3 years ago.
Code:
public class Crate {
private final MapPosition cratePosition;
private final int tierId;
#Expose(serialize = false, deserialize = false)
private final Inventory inventory;
public Crate(MapPosition cratePosition, int tierId) {
this.cratePosition = cratePosition;
this.tierId = tierId;
this.inventory = Bukkit.createInventory(null, 9*3, "Supply Crate");
}
public void replenishCrates(CrateConfig config) {
List<CrateContent> contents = config.getContentByTier(tierId);
//TODO:
}
public Inventory getInventory() {
return inventory;
}
public Location toLocation(World world) {
return cratePosition.toLocation(world);
}
public MapPosition getCratePosition() {
return cratePosition;
}
public int getTierId() {
return tierId;
}}
The #Expose is being ignored and returning a null pointer exception when trying to deserialize and serialize the class contents. I have made sure to also include the correct GsonBuilder modifications, as stated in Gson's documentation.
The problem you are having is not because #Expose is being ignored but rather because #Expose is missing on the other attributes.
The GsonBuilder's modification you are refering to is the following:
This annotation has no effect unless you build Gson with a GsonBuilder and invoke GsonBuilder.excludeFieldsWithoutExposeAnnotation() method.
But thank God the authors have correctly named the method and it will do just what it is expressing: it will exclude every field that is not marked with the #Expose annotation.
Here is an illustration based on your code (a little bit different because you did not share a completely reproductible sample)
public class Crate {
private final String cratePosition;
private final int tierId;
#Expose(serialize = false, deserialize = false)
private final Inventory inventory;
public Crate(String cratePosition, int tierId) {
this.cratePosition = cratePosition;
this.tierId = tierId;
this.inventory = new Inventory("IV-ID-111000", 10200);
}
public Inventory getInventory() {
return inventory;
}
public int getTierId() {
return tierId;
}
public String getCratePosition() {
return cratePosition;
}
}
And the following test:
public static void main(String[] args) {
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
Crate crate = new Crate("484:125.52", 1250);
String jsonString = gson.toJson(crate);
System.out.println(jsonString);
String json = "{\"cratePosition\":\"4894:125.52\",\"tierId\":2350}";
Crate deserialized = gson.fromJson(json, Crate.class);
System.out.println(deserialized.getCratePosition() + ":" + deserialized.getTierId());
}
In the current case I have no #Expose annotation on cratePosition and tierId fields, so they are excluded from the serialization and deserialization. Therefore my test returns:
{}
null:0
Now let's add the #Expose annotation on the cratePosition and tierId fields in the Crate class:
#Expose()
private final String cratePosition;
#Expose()
private final int tierId;
By default the serialize and deserialize parameters of the #Expose annotation are both set to true. You can play with it and change the values to see the differences it produces.
If I run the test again I have:
{"cratePosition":"484:125.52","tierId":1250}
4894:125.52:2350

How to get dot class of list of Objects?

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);

'dynamic'-like java annotations?

I have a pojo that is dependent on annotations. It has predefined fields as well as a Set that contains user provided fields:
public class MyPOJO implements Document {
private String id;
private LocalString name;
private LocalString desc;
private List<Field> fields;
public MyPOJO(final String id,
final LocalString name,
final LocalString desc,
final List<Field> fields) {
this.id = id;
this.name = name;
this.desc = desc;
this.fields = fields;
}
public String getId() {
return id;
}
#Indexed(searchable = false, stored = true)
public LocalString getName() {
return name;
}
#Indexed(searchable = true)
public LocalString getDescription() {
return desc;
}
public List<Field> getFields() {
return fields;
}
}
MyPOJO is a 'generic' object, ie, the developer (or consumer) of MyPOJO has fields that are not predefined in MyPOJO and therefore the developer needs to place these additional fields the in attribute 'fields'. The problem arises from the fact that each object in the Set fields needs to have its own annotations to indicate whether the particular field is either stored or searchable in order to remain consistent with the predefined attributes, such as name.
I can think of two options:
For each additional field, the developer will have to create an
anonymous class implementing the interface Field and inside this
anonymous class, the developer will declare the applicable
annotations.
the Set 'fields' contains a complex object of fieldname, fieldvalue
and annotations as shown below. I can't figure out how to invoke the constructor for Field. The below code does not compile but it is intended as pseudo-code to signify what I am trying to do.
Field myfield1 = new Field("dateofBirth", new Date(), new ArrayList({Index.stored, Index.searchable});
Field myfield2 = new Field("model", "330i", new ArrayList({Index.stored});
There is no construct to pass annotations as a parameter: new ArrayList({Index.stored}.
public class Field {
private String name;
private Object value;
Collection<Annotation> annotations;
public Field(final String name, final Object value, Collection<Annotation> annotations;) {
this.name = name;
this.value = value;
this.annotations = Collections.unmodifiableCollection(annotations);
}
public String getName() {
return name;
}
public Object getValue() {
return value;
}
}
I'm not particularly excited with either option and hoping someone can give me some pointers
If you need an extensible object model, I'd say a POJO design is just setting yourself up for extra work as opposed to exposing a metamodel.
That said, what you could do is have clients of the API subclass MyPOJO, and annotate the properties they define in their subclasses. You would then use reflection to go through all JavaBeans properties of the objects you're receiving and determine the annotations on the getters - similarly to how JPA works.

Categories

Resources