Create difference object with JaVers - java

Perhaps I'm completely misunderstanding the purpose of JaVers, but I'm struggling here.
I got this POJO with a few nested subclasses, and I would like to create a new instance of that object ONLY containing the differences between two other instances, every matching field should stay null in the new class. Here is what I mean:
public class RootClass {
private String someString;
private String someOtherString;
private ANestedClass aNestedClass;
public RootClass(String someString, String someOtherString, ANestedClass aNestedClass) {
this.someString = someString;
this.someOtherString = someOtherString;
this.aNestedClass = aNestedClass;
}
public static class ANestedClass {
private String someNestedString;
private String someOtherNestedString;
public ANestedClass(String someNestedString, String someOtherNestedString) {
this.someNestedString = someNestedString;
this.someOtherNestedString = someOtherNestedString;
}
}
}
public RootClass getDifferenceObject() {
RootClass a = new RootClass("foo", "bar", new ANestedClass("hello", "world"));
RootClass b = new RootClass("foo", "blabla", new ANestedClass("hello", "earth");
Javers javers = JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE).build();
Diff compare = javers.compare(a, b);
// now convert this somehow
RootClass abDiff = ...;
assert(abDiff.someString == null);
assert(abDiff.someOtherString.equals("blabla"));
assert(abDiff.aNestedClass.someNestedString == null);
assert(abDiff.aNestedClass.someOtherNestedString.equals("earth"));
}
Is this even possible with JaVers? I'm happy to pick a different library as well.

Related

Jackson ObjectMapper: How to omit (ignore) fields of certain type from serialization?

How can I tell Jackson ObjectMapper to ignore fields of certain type (class), in my case of Object.class, from serialization?
Constrains:
No control of source class - it is a third party class
Class type being serialized is unknown upfront - I guess it disqualifies MixIn(s)
Such field(s) name is unknown upfront
To help, below is a unit test expecting fields objectsList and objectField to be ignored from serialization, but its approach is not correct, it is filtering them by name instead of by their type.
public static class FavoriteShows {
public Simpsons favorite = new Simpsons();
public BigBangTheory preferred = new BigBangTheory();
}
public static class Simpsons {
public String title = "The Simpsons";
public List<Object> objectsList = List.of("homer", "simpson");
public Object objectField = new HashMap() {{
put("mr", "burns");
put("ned", "flanders");
}};
}
public static class BigBangTheory {
public String title = "The Big Bang Theory";
public List<Object> objectsList = List.of("sheldon", "cooper");
public Object objectField = new HashMap() {{
put("leonard", "hofstadter");
put("Raj", "koothrappali");
}};
}
public abstract static class MyMixIn {
#JsonIgnore
private Object objectField;
#JsonIgnore
private Object objectsList;
}
#Test
public void test() throws JsonProcessingException {
// GIVEN
// Right solution must work for any (MixIn(s) is out of questions) Jackson annotated class
// without its modification.
final ObjectMapper mapper = new ObjectMapper()
.addMixIn(Simpsons.class, MyMixIn.class)
.addMixIn(BigBangTheory.class, MyMixIn.class);
// WHEN
String actual = mapper.writeValueAsString(new FavoriteShows());
System.out.println(actual);
// THEN
// Expected: {"favorite":{"title":"The Simpsons"},"preferred":{"title":"The Big Bang Theory"}}
assertThat(actual).isEqualTo("{\"favorite\":{\"title\":\"The Simpsons\"},\"preferred\":{\"title\":\"The Big Bang Theory\"}}");
}
One of the way is to use custom AnnotationIntrospector.
class A {
int three = 3;
int four = 4;
B b = new B();
// setters + getters
}
class B {
int one = 1;
int two = 2;
// setters + getters
}
To ignore all fields with type B:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
protected boolean _isIgnorable(Annotated a) {
return super._isIgnorable(a)
// Ignore B.class
|| a.getRawType() == B.class
// Ignore List<B>
|| a.getType() == TypeFactory.defaultInstance()
.constructCollectionLikeType(List.class, B.class);
}
});
String json = mapper.writeValueAsString(new A());
If you are using mixins you should be able to annotate with #JsonIgnoreType to have it ignore the class. docs For reference Globally ignore class in Jackson

Dynamically copy primitive as well as complex object type fields in java with auto conversion

I want to copy some fields from one java object to other java object dynamically. Classes of these objects are different.
Below is the scenario
I have some resource classes with some fields. There are few fields
in each class which can be modified by user but these fields are
not same in all the resource classes. List of modifiable fields
for each class is being maintained in some data structure.
Most of the fields are similar and some are convertible like one has
primitive and other has wrapper class. Some fields should be
converted from Number types to String or vise versa as well.
Most of the fields are complex object types.
Which of the libraries available today will help me copying these fields with auto-conversion where ever required and deep copy ?
Earlier I was using BeanUtils but this does not deep copy. Neither it supports auto conversion.
I thought spring framework might help.Here's my example of spring-core-4.1.5:
class declaration:
public static class A{
private String a;
private String b;
private String c;
private C d;
private int e;
//getter&setter here
public static class C {
private String aa;
private String bb;
private String cc;
//getter&setter here
public static class B{
private Integer a;
private Long b;
private Boolean c;
private D d;
private Integer e;
//getter&setter here
public static class D {
private Integer aa;
private Long bb;
private Boolean cc;
//getter&setter here
class ObjectConverter implements Converter<Object, Object> {
private final ConversionService conversionService;
private final Class targetClass;
public ObjectConverter(ConversionService conversionService, Class targetClass) {
this.conversionService = conversionService;
this.targetClass = targetClass;
}
#Override
public Object convert(Object source) {
Object ret=null;
try {
ret = targetClass.newInstance();
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
PropertyDescriptor sourceDescriptor = new PropertyDescriptor(field.getName(),source.getClass());
PropertyDescriptor targetDescriptor = new PropertyDescriptor(field.getName(),targetClass);
if (sourceDescriptor.getReadMethod()==null || targetDescriptor.getWriteMethod() == null) {
//record error here
break;
}
Class<?> sourcePType = sourceDescriptor.getPropertyType();
Class<?> targetPType = targetDescriptor.getPropertyType();
if(conversionService.canConvert(sourcePType,targetPType)){
Object sourceValue = sourceDescriptor.getReadMethod().invoke(source);
Object targetValue = conversionService.convert(sourceValue, targetPType);
targetDescriptor.getWriteMethod().invoke(ret,targetValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
}
main method:
A orgA = new A();
orgA.a="123";
orgA.b="1234567";
orgA.c="1";
orgA.d = new C();
orgA.d.aa="321";
orgA.d.bb="7654321";
orgA.d.cc="0";
orgA.e=-99;
System.out.println(orgA);
try {
ConfigurableConversionService conversionService = new DefaultConversionService();
//if you are not satisfied with the final A result, then
//customize your own Boolean->String Converter;
// conversionService.addConverter(new Converter<Boolean, String>() {
// #Override
// public String convert(Boolean source) {
// return source==null||!source?"0":"1";
// }
// });
conversionService.addConverter(A.class, B.class, new ObjectConverter(conversionService, B.class));
conversionService.addConverter(B.class, A.class, new ObjectConverter(conversionService, A.class));
conversionService.addConverter(C.class, D.class, new ObjectConverter(conversionService, D.class));
conversionService.addConverter(D.class, C.class, new ObjectConverter(conversionService, C.class));
B b = conversionService.convert(orgA, B.class);
System.out.println(b);
A a = conversionService.convert(b, A.class);
System.out.println(a);
} catch (Exception e) {
e.printStackTrace();
}
results:
A{a='123', b='1234567', c='1', d=C{aa='321', bb='7654321', cc='0'}, e=-99}
B{a=123, b=1234567, c=true, d=D{aa=321, bb=7654321, cc=false}, e=-99}
A{a='123', b='1234567', c='true', d=C{aa='321', bb='7654321', cc='false'}, e=-99}

Array with 2 values per item

I'm trying to make an array with 2 values per item, like a value with a custom header, and, coming from Ruby, can't find a correct way to do this in Java.
This is for a REST-assured tests that i need to automate.
This is the method that i need to do, I mix the declaration of obj with some sort of ruby way to do it, so the necessity it's more clear:
private String[] getHeaders() {
String[] obj = [
'Signature' => this.getSignature(),
'Timestamp' => this.getTimestamp(),
];
if(getSessionToken() != null) {
obj.sessionToken = this.getSessionToken();
}
}
}
You can achieve that by creating a model. For example:
public class MyModel {
private String signature;
private String timestamp;
public MyModel() {
// constructor
}
public MyModel(String signature, String timestamp){
this.signature = signature;
this.timestamp = timestamp;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
Then create an array of your model. You can use:
private static final int MODEL_SIZE = 5;
private MyModel[] models = new MyModel[MODEL_SIZE];
if you already know the size of your array. Or you can use this approach below if you don't know the size of array yet:
private ArrayList<MyModel> models = new ArrayList<>;
private MyModel model;
// Then fill your models
// by using this way
model = new MyModel("My Signature", "My Timestamp");
models.add(model);
// or this way
model = new MyModel();
model.setSignature("My Signature");
model.setTimestamp("My Timestamp");
models.add(model);
Another way to achieve that without creating a model is by using HashMap. This is the example:
List<HashMap<String, String>> objects = new ArrayList<>();
HashMap<String, String> object = new HashMap<>();
object.put("signature", "My Signature");
object.put("timestamp", "My Timestamp");
objects.add(object);
Something like this I suspect is what you want.
class Headers{
public String signature;
public String timeStamp;
}
Headers[] header = new Headers[10];
You probably don't need getters and setters, but you can throw those in too.

Gson serialize a list of polymorphic objects

I'm trying to serialize/deserialize an object, that involves polymorphism, into JSON using Gson.
This is my code for serializing:
ObixBaseObj lobbyObj = new ObixBaseObj();
lobbyObj.setIs("obix:Lobby");
ObixOp batchOp = new ObixOp();
batchOp.setName("batch");
batchOp.setIn("obix:BatchIn");
batchOp.setOut("obix:BatchOut");
lobbyObj.addChild(batchOp);
Gson gson = new Gson();
System.out.println(gson.toJson(lobbyObj));
Here's the result:
{"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch"}]}
The serialization mostly works, except its missing the contents of inherited members (In particular obix:BatchIn and obixBatchout strings are missing).
Here's my base class:
public class ObixBaseObj {
protected String obix;
private String display;
private String displayName;
private ArrayList<ObixBaseObj> children;
public ObixBaseObj()
{
obix = "obj";
}
public void setName(String name) {
this.name = name;
}
...
}
Here's what my inherited class (ObixOp) looks like:
public class ObixOp extends ObixBaseObj {
private String in;
private String out;
public ObixOp() {
obix = "op";
}
public ObixOp(String in, String out) {
obix = "op";
this.in = in;
this.out = out;
}
public String getIn() {
return in;
}
public void setIn(String in) {
this.in = in;
}
public String getOut() {
return out;
}
public void setOut(String out) {
this.out = out;
}
}
I realize I could use an adapter for this, but the problem is that I'm serializing a collection of base class type ObixBaseObj. There are about 25 classes that inherits from this. How can I make this work elegantly?
There's a simple solution: Gson's RuntimeTypeAdapterFactory (from com.google.code.gson:gson-extras:$gsonVersion). You don't have to write any serializer, this class does all work for you. Try this with your code:
ObixBaseObj lobbyObj = new ObixBaseObj();
lobbyObj.setIs("obix:Lobby");
ObixOp batchOp = new ObixOp();
batchOp.setName("batch");
batchOp.setIn("obix:BatchIn");
batchOp.setOut("obix:BatchOut");
lobbyObj.addChild(batchOp);
RuntimeTypeAdapterFactory<ObixBaseObj> adapter =
RuntimeTypeAdapterFactory
.of(ObixBaseObj.class)
.registerSubtype(ObixBaseObj.class)
.registerSubtype(ObixOp.class);
Gson gson2=new GsonBuilder().setPrettyPrinting().registerTypeAdapterFactory(adapter).create();
Gson gson = new Gson();
System.out.println(gson.toJson(lobbyObj));
System.out.println("---------------------");
System.out.println(gson2.toJson(lobbyObj));
}
Output:
{"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch","children":[]}]}
---------------------
{
"type": "ObixBaseObj",
"obix": "obj",
"is": "obix:Lobby",
"children": [
{
"type": "ObixOp",
"in": "obix:BatchIn",
"out": "obix:BatchOut",
"obix": "op",
"name": "batch",
"children": []
}
]
}
EDIT: Better working example.
You said that there are about 25 classes that inherits from ObixBaseObj.
We start writing a new class, GsonUtils
public class GsonUtils {
private static final GsonBuilder gsonBuilder = new GsonBuilder()
.setPrettyPrinting();
public static void registerType(
RuntimeTypeAdapterFactory<?> adapter) {
gsonBuilder.registerTypeAdapterFactory(adapter);
}
public static Gson getGson() {
return gsonBuilder.create();
}
Every time we need a Gson object, instead of calling new Gson(), we will call
GsonUtils.getGson()
We add this code to ObixBaseObj:
public class ObixBaseObj {
protected String obix;
private String display;
private String displayName;
private String name;
private String is;
private ArrayList<ObixBaseObj> children = new ArrayList<ObixBaseObj>();
// new code
private static final RuntimeTypeAdapterFactory<ObixBaseObj> adapter =
RuntimeTypeAdapterFactory.of(ObixBaseObj.class);
private static final HashSet<Class<?>> registeredClasses= new HashSet<Class<?>>();
static {
GsonUtils.registerType(adapter);
}
private synchronized void registerClass() {
if (!registeredClasses.contains(this.getClass())) {
registeredClasses.add(this.getClass());
adapter.registerSubtype(this.getClass());
}
}
public ObixBaseObj() {
registerClass();
obix = "obj";
}
Why? because every time this class or a children class of ObixBaseObj is instantiated,
the class it's gonna be registered in the RuntimeTypeAdapter
In the child classes, only a minimal change is needed:
public class ObixOp extends ObixBaseObj {
private String in;
private String out;
public ObixOp() {
super();
obix = "op";
}
public ObixOp(String in, String out) {
super();
obix = "op";
this.in = in;
this.out = out;
}
Working example:
public static void main(String[] args) {
ObixBaseObj lobbyObj = new ObixBaseObj();
lobbyObj.setIs("obix:Lobby");
ObixOp batchOp = new ObixOp();
batchOp.setName("batch");
batchOp.setIn("obix:BatchIn");
batchOp.setOut("obix:BatchOut");
lobbyObj.addChild(batchOp);
Gson gson = GsonUtils.getGson();
System.out.println(gson.toJson(lobbyObj));
}
Output:
{
"type": "ObixBaseObj",
"obix": "obj",
"is": "obix:Lobby",
"children": [
{
"type": "ObixOp",
"in": "obix:BatchIn",
"out": "obix:BatchOut",
"obix": "op",
"name": "batch",
"children": []
}
]
}
I hope it helps.
I think that a custom serializer/deserializer is the only way to proceed and I tried to propose you the most compact way to realize it I have found. I apologize for not using your classes, but the idea is the same (I just wanted at least 1 base class and 2 extended classes).
BaseClass.java
public class BaseClass{
#Override
public String toString() {
return "BaseClass [list=" + list + ", isA=" + isA + ", x=" + x + "]";
}
public ArrayList<BaseClass> list = new ArrayList<BaseClass>();
protected String isA="BaseClass";
public int x;
}
ExtendedClass1.java
public class ExtendedClass1 extends BaseClass{
#Override
public String toString() {
return "ExtendedClass1 [total=" + total + ", number=" + number
+ ", list=" + list + ", isA=" + isA + ", x=" + x + "]";
}
public ExtendedClass1(){
isA = "ExtendedClass1";
}
public Long total;
public Long number;
}
ExtendedClass2.java
public class ExtendedClass2 extends BaseClass{
#Override
public String toString() {
return "ExtendedClass2 [total=" + total + ", list=" + list + ", isA="
+ isA + ", x=" + x + "]";
}
public ExtendedClass2(){
isA = "ExtendedClass2";
}
public Long total;
}
CustomDeserializer.java
public class CustomDeserializer implements JsonDeserializer<List<BaseClass>> {
private static Map<String, Class> map = new TreeMap<String, Class>();
static {
map.put("BaseClass", BaseClass.class);
map.put("ExtendedClass1", ExtendedClass1.class);
map.put("ExtendedClass2", ExtendedClass2.class);
}
public List<BaseClass> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
List list = new ArrayList<BaseClass>();
JsonArray ja = json.getAsJsonArray();
for (JsonElement je : ja) {
String type = je.getAsJsonObject().get("isA").getAsString();
Class c = map.get(type);
if (c == null)
throw new RuntimeException("Unknow class: " + type);
list.add(context.deserialize(je, c));
}
return list;
}
}
CustomSerializer.java
public class CustomSerializer implements JsonSerializer<ArrayList<BaseClass>> {
private static Map<String, Class> map = new TreeMap<String, Class>();
static {
map.put("BaseClass", BaseClass.class);
map.put("ExtendedClass1", ExtendedClass1.class);
map.put("ExtendedClass2", ExtendedClass2.class);
}
#Override
public JsonElement serialize(ArrayList<BaseClass> src, Type typeOfSrc,
JsonSerializationContext context) {
if (src == null)
return null;
else {
JsonArray ja = new JsonArray();
for (BaseClass bc : src) {
Class c = map.get(bc.isA);
if (c == null)
throw new RuntimeException("Unknow class: " + bc.isA);
ja.add(context.serialize(bc, c));
}
return ja;
}
}
}
and now this is the code I executed to test the whole thing:
public static void main(String[] args) {
BaseClass c1 = new BaseClass();
ExtendedClass1 e1 = new ExtendedClass1();
e1.total = 100L;
e1.number = 5L;
ExtendedClass2 e2 = new ExtendedClass2();
e2.total = 200L;
e2.x = 5;
BaseClass c2 = new BaseClass();
c1.list.add(e1);
c1.list.add(e2);
c1.list.add(c2);
List<BaseClass> al = new ArrayList<BaseClass>();
// this is the instance of BaseClass before serialization
System.out.println(c1);
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(al.getClass(), new CustomDeserializer());
gb.registerTypeAdapter(al.getClass(), new CustomSerializer());
Gson gson = gb.create();
String json = gson.toJson(c1);
// this is the corresponding json
System.out.println(json);
BaseClass newC1 = gson.fromJson(json, BaseClass.class);
System.out.println(newC1);
}
This is my execution:
BaseClass [list=[ExtendedClass1 [total=100, number=5, list=[], isA=ExtendedClass1, x=0], ExtendedClass2 [total=200, list=[], isA=ExtendedClass2, x=5], BaseClass [list=[], isA=BaseClass, x=0]], isA=BaseClass, x=0]
{"list":[{"total":100,"number":5,"list":[],"isA":"ExtendedClass1","x":0},{"total":200,"list":[],"isA":"ExtendedClass2","x":5},{"list":[],"isA":"BaseClass","x":0}],"isA":"BaseClass","x":0}
BaseClass [list=[ExtendedClass1 [total=100, number=5, list=[], isA=ExtendedClass1, x=0], ExtendedClass2 [total=200, list=[], isA=ExtendedClass2, x=5], BaseClass [list=[], isA=BaseClass, x=0]], isA=BaseClass, x=0]
Some explanations: the trick is done by another Gson inside the serializer/deserializer. I use just isA field to spot the right class. To go faster, I use a map to associate the isA string to the corresponding class. Then, I do the proper serialization/deserialization using the second Gson object. I declared it as static so you won't slow serialization/deserialization with multiple allocation of Gson.
Pro
You actually do not write more code than this, you let Gson do all the work. You have just to remember to put a new subclass into the maps (the exception reminds you of that).
Cons
You have two maps. I think that my implementation can refined a bit to avoid map duplications, but I left them to you (or to future editor, if any).
Maybe you want to unify serialization and deserialization into a unique object, you should be check the TypeAdapter class or experiment with an object that implements both interfaces.
I appreciate the other answers here that led me on my path to solving this issue. I used a combination of RuntimeTypeAdapterFactory with Reflection.
I also created a helper class to make sure a properly configured Gson was used.
Within a static block inside the GsonHelper class, I have the following code go through my project to find and register all of the appropriate types. All of my objects that will go through JSON-ification are a subtype of Jsonable.
You will want to change the following:
my.project in Reflections should be your package name.
Jsonable.class is my base class. Substitute yours.
I like having the field show the full canonical name, but clearly if you don't want / need it, you can leave out that part of the call to register the subtype. The same thing goes for className in the RuntimeAdapterFactory; I have data items already using the type field.
private static final GsonBuilder gsonBuilder = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.excludeFieldsWithoutExposeAnnotation()
.setPrettyPrinting();
static {
Reflections reflections = new Reflections("my.project");
Set<Class<? extends Jsonable>> allTypes = reflections.getSubTypesOf(Jsonable.class);
for (Class< ? extends Jsonable> serClass : allTypes){
Set<?> subTypes = reflections.getSubTypesOf(serClass);
if (subTypes.size() > 0){
RuntimeTypeAdapterFactory<?> adapterFactory = RuntimeTypeAdapterFactory.of(serClass, "className");
for (Object o : subTypes ){
Class c = (Class)o;
adapterFactory.registerSubtype(c, c.getCanonicalName());
}
gsonBuilder.registerTypeAdapterFactory(adapterFactory);
}
}
}
public static Gson getGson() {
return gsonBuilder.create();
}
I created a type adapter factory that uses an annotation and ClassGraph to discover subclasses and supports multiple serialization styles (Type Property, Property, Array). See github for source code and maven coordinates.

deserialize nested arbitrary class in java with gson

i need to convert the json-string tmp =>
{"result_count":1,"next_offset":1,"entry_list":[{"id":"xyz123","module_name":"Products","name_value_list":{"id":{"name":"id","value":"xyz123"},"name":{"name":"name","value":"test_product_2"}}}],"relationship_list":[]}
into a corresponding java-pojo
my pojo looks like
public class GetEntryListResponse {
public int result_count = 0;
public int next_offset = 0;
public List<EntryList> entryList = new ArrayList<EntryList>();
public static class EntryList {
String id = "";
String module_name = "";
public static class NameValueList {
public static class Id {
String name = "";
String value = "";
}
public static class Name {
String name = "";
String value = "";
}
}
}
}
and for the deserilizing-task a use
Gson json_response = new Gson();
GetEntryListResponse resp = json_response.fromJson(tmp,
GetEntryListResponse.class);
i also tried other variants but this one seems to be the best so far. the problem is that result_count and next_offset are transformed into int but the type of the array entryList is with null-values.
Implement InstanceCreator and JsonDeserializer for your class
public class GetEntryListResponse implements
InstanceCreator<GetEntryListResponse>,
JsonDeserializer<GetEntryListResponse>{
#Override
public GetEntryListResponse createInstance(Type type) {
return this;
}
#Override
public GetEntryListResponse deserialize(JsonElement json, Type typeOfT){
json.getJsonObject();//
// create your classes objects here by json key
}
and use
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.registerTypeAdapter(GetEntryListResponse.class,
new GetEntryListResponse()).create();
try changing:
public List<EntryList> entryList = new ArrayList<EntryList>();
to:
public List<EntryList> entry_list= new ArrayList<EntryList>();
and deserialize.

Categories