I need help with the gson library, basically i have to de-serialize a wrapper class defined like this:
static class WrapperClass {
public int value;
private final String otherData;
public WrapperClass(String otherData) {
this.otherData = otherData;
}
}
The class that I have to "jsonize" has fields like this:
private final WrapperClass wrappedData = new WrapperClass("other data");
The serializer of the wrapper class is really simple:
#Override
public JsonElement serialize(WrapperClass src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src.value);
}
The problem is the de-serialization side, I should not re-instantiate the WrapperClass object because it's already there by default, i have only to de-serialize it's value, it's possible to this with the gson library?
So, what you want to do: "read" the content of some JSON string and "overlay" existing objects with that.
I am not aware of a built-in way to do that. Beyond that: doing something of this complexity under the covers sounds like the wrong approach, too.
Thus, my recommendation: clearly separate those responsibilities. Meaning: instead of of creating a (almost counter-intuitive) solution that merges JSON data "into" existing objects: do that explicitly, it step by step. Like:
read the JSON data and de-serialize into one or more objects
then have an another special component "update" your "old" objects with the information found in the de-serialized objects
Related
I have two enums I've created for my Java project, and both of them need to be serialized and deserialized using Gson. The problem is, I need to use a value on each enum Field as the serialized value.
As an example, I have these enums:
Options Enum
Language Enum
My hope is that I am able to serialize both enums using the key value provided to both. This is a really simplified example, but it still perfectly describes my situation.
I tried using custom serializer classes for both:
Options Serializer
Language Serializer
And yes, I did register both using registerTypeAdapter(type, adapter)
The strange this is, it would work for one enum, serializing to the correct value, but not the other. I suspect it's because the class that's being serialized is formatted similar to this:
public class Item {
public Language language;
public List<Options> options;
}
Where in this case, Language is serialized properly, but the Options enum is not, just returning the enum value name.
I'm not sure if there's some special way I need to handle this, but it's getting frustrating.
EDIT: I know about using the #SerializedName() annotation, but both of the enums I'm using have hundreds of entries and the keys that are part of the enum are used elsewhere throughout the program as well. Using #SerializedName(), at least in my case, I don't think would be feasible.
Write an adapter for Item. I would add more but laptop on 1%.
static class ItemAdapter extends TypeAdapter<Item> {
#Override
public void write(JsonWriter out, Item value) throws IOException {
out.beginObject();
out.name("language").value(value.language.key);
out.name("options");
out.beginArray();
for (Option option : value.options) {
out.value(option.key);
}
out.endArray();
out.endObject();
}
#Override
public Item read(JsonReader in) throws IOException {
in.beginObject();
in.nextName();
String languageKey = in.nextString();
in.nextName();
in.beginArray();
List<String> optionKeys = new ArrayList<>();
while (in.hasNext()) {
optionKeys.add(in.nextString());
}
in.endArray();
in.endObject();
return new Item(Language.BY_KEY.get(languageKey),
optionKeys.stream()
.map(Option.BY_KEY::get)
.collect(Collectors.toList()));
}
}
so this is a very weird situation that I came across and have no idea how to handle.
I have a service that contains logic to deserialize an object from a request sent to it. Now the object in question is being changed and a new deserialization method is implemented to deal with that (don't ask why, all I know is we just need to change the method of deserialization).
The problem is that this change needs to be backwards compatible so we should be able to deal with both types of objects. In order to do this, we need to be able to determine the correct deserializer to use depending on the type of object but how do we do that if the object is serialized into a byte buffer? Im out of ideas... Is there a different/better way of going about this change?
The service is in Java.
Edit 1: Clearing up my intentions .
The old object used a custom serializer and the new one uses an ObjectMapper JSON serializer. So my goal is to be able to detect if I am dealing with the old or new object so I can deserialize accordingly.
I can try to use the new deserializer and catch the JsonParseException it throws and in the catch block, use the old serializer but this is not the way I want to handle a JsonParseException.
Serializable classes should have a serialVersionUID that is static, final, and of type long.
This is to ensure that the class of the object that was serialized is same as the class of the object being deserialized.
In order to achieve backward compatibility follow these steps:
Ensure that whenever class structure is changed, you change the value of this field.
Use your new custom serializer to deserialize the object.
If the object is of the previous class you will get an InvalidClassException. Catch this exception and try to deserialize that object with the legacy deserializer inside catch block.
This ensures that your custom deserializer has backward compatability.
First of all, you need to identify a difference between the new and the old object. You'll use that to switch to the old de-serializer.
You'll also need a specific ObjectMapper for these two classes, old and new.
Create a Module, and register it
final SimpleModule module = new SimpleModule();
module.addDeserializer(Object.class, new NewDeserializer(new OldDeserializer()));
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
Prepare the new StdDeserializer, which will accept as constructor argument the old one.
public class NewDeserializer extends StdDeserializer<Object> {
private final StdDeserializer<Object> oldDeserializer;
NewDeserializer(final StdDeserializer<Object> oldDeserializer) {
super(NewObject.class);
this.oldDeserializer = oldDeserializer;
}
#Override
public Object deserialize(
final JsonParser parser,
final DeserializationContext context) throws IOException {
final ObjectCodec codec = parser.getCodec();
// Read the JSON document to a tree
final TreeNode treeNode = codec.readTree(parser);
// Identify if it is the new format, or the old one
final TreeNode newField = treeNode.get("newField");
if (newField == null) {
// Delegate to the old de-serializer
final JsonFactory factory = new JsonFactory(parser.getCodec());
final JsonParser oldParser = factory.createParser(treeNode.toString());
return oldDeserializer.deserialize(oldParser, context);
}
return codec.readValue(treeNode.traverse(), NewObject.class);
}
}
The old StdDeserializer
public class OldDeserializer extends StdDeserializer<Object> {
OldDeserializer() {
super(OldObject.class);
}
#Override
public Object deserialize(
final JsonParser parser,
final DeserializationContext context) throws IOException {
return parser.getCodec().readValue(parser, OldObject.class);
}
}
Now, simply call
objectMapper.readValue(v, Object.class);
After multiple researches on Google and Stack Overflow, i haven't found a similar case to mine.
I need to use Gson library to convert a Java object to Json. The fact is that this object contains a field with a custom generic type, as follow :
SendData.java :
public class SendData {
private SendDataRequestObject<?> sendData;
// Constructor + Getters and Setters
}
Here is the class definition of SendDataRequestObject :
public class SendDataRequestObject<T> {
private String actionType;
private T parameters;
private CustomClass customClass;
//Constructor + Getters and Setters
}
And finally, the class definition of MyRequest which may be injected in SendDataRequestObject as the T parameter
public class MyRequest {
private Map<Integer, String> myMap;
private String myString1;
private String myString2;
//Constructor + Getters and Setters
}
Actually, I'm able to parse SendDataRequestObject with Gson library as follow :
SendDataRequestObject<MyRequest> requestObject = new SendDataRequestObject<MyRequest>();
//...
//Initializing and adding fields to requestObject
//...
Type token = new TypeToken<SendDataRequestObject<MyRequest>>(){}.getType();
System.out.println(new GsonBuilder().create().toJson(requestObject, token));
The output is properly set and every fields, even the generic one, are included into the final json string :
{"actionType":"verify","parameters":{"myMap":{"15789":"hreher-489hre-gdsf","13057":"rtyuiop-4g8ezg","16741":"gfd456-uiop789"},"myString1":"myStringValue1","myString2":"myStringValue2"},"customClas":{"attr1":"value1","attr2":"value2"}}
But what I need is to parse SendData class, not SendDataRequestObject class. When I try to convert this class into json string, I obtain this output :
{"sendData":{"actionType":"verify","parameters":{},"customClass":{"attr1":"value1","attr2":"value2"}}}
So, we can see that parameters field of SendDataRequestObject is not converted to Json, probably because this is a generic class.
If anybody has an idea of how to do it, I would be very grateful !
You can't do this without somehow knowing the type T at compile time in some manner due to Java's type erasure.
One option for this is the JSON can contain some information specifying the type, e.g.
{
"sendDataType": "MyRequest",
"sendData": {
...
}
}
If you then make SendData generic e.g.
SendData<T> {
private SendDataRequestObject<T> sendData;
}
you can then parse the JSON once to find out the sendDataType:
SendData<?> genericSendData = new GsonBuilder().create().toJson(requestObject, new TypeToken<SendData<?>>(){});
String sendDataType = genericSendData.sendDataType;
and use that to create a TypeToken of the right type:
switch(sendDataType) {
case "MyRequest":
return new TypeToken<MyRequest>(){};
}
And then parse the JSON again specifying the generic type now that you know it:
SendData<?> myRequestSendData = new GsonBuilder().create().toJson(requestObject, typeToken);
This works because our switch statement knows the possible types at compile time and can create TypeTokens for them.
I'm having some doubts about software design when parsing an XML. Usually, I will create a class for every meaningful tag in the XML and then from the parser fill it with the attributes.
setElementListener(new ElementListener() {
#Override
public void start(Attributes attributes) {
Object obj = new Object();
obj.setAttribute(attributes.get("attName");
}
}
Now, I have a big XML with many differentes objects, so my XMLParser class is becoming unstoppable from growing. We discussed that maybe in our object class create a static method that will handle the creation of the object from the attributes.
public static Object inflate(Attributes attributes) {
Object result = new Object();
obj.setAttribute(attributes.get("attName");
return result;
}
Which of both ways is better?
EDIT:
The main problem with the second option is that I don't like the data class to know what Attributes is, it seems like not a good design.
Id like to represent a Class object as JSON. For example, if I have the class defintions as follows:
public class MyClass {
String myName;
int myAge;
MyOtherClass other;
}
public class MyOtherClass {
double myDouble;
}
I'd like to get the following nested JSON from a Class object of type MyClass:
{
myName: String,
myAge: int,
other: {
myDouble: double;
}
}
EDIT:
I don't want to serialize instances of these classes, I understand how to do that with GSON. I want to serialize the structure of the class itself, so that given a proprietary class Object I can generate JSON that breaks down the fields of the class recursively into standard objects like String, Double, etc.
With Jettison, you can roll your own mappings from Java to JSON. So in this case, you could get the Class object of the class you want, then map the Java returned by the getFields, getConstructors, getMethods etc. methods to JSON using Jettison.
I would recommend to use Jackson.
You can also take a look at the JSonObjectSerializer class based on Jackson which can be found at oVirt under engine/backend/manager/module/utils (you can git clone the code) and see how we used Jackson there.
Looking to do the same thing, in the end I wound up writing my own method, this does not handle all cases e.g. if one of the declared fields is a Map this will break, but this seems to be alright for most common objects:
#Override
public Map reflectModelAsMap(Class classType) {
List<Class> mappedTracker = new LinkedList<Class>();
return reflectModelAsMap(classType, mappedTracker);
}
private Map reflectModelAsMap(Class classType, List mappedTracker) {
Map<String, Object> mapModel = new LinkedHashMap<String, Object>();
mappedTracker.add(classType);
Field[] fields = classType.getDeclaredFields();
for (Field field : fields) {
if (mappedTracker.contains(field.getType()))
continue;
if (BeanUtils.isSimpleValueType(field.getType())) {
mapModel.put(field.getName(), field.getType().toString());
} else if (Collection.class.isAssignableFrom(field.getType())) {
Class actualType = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
mapModel.put("Collection", reflectModelAsMap(actualType, mappedTracker));
} else {
mapModel.put(field.getName(), reflectModelAsMap(field.getType(), mappedTracker));
}
}
return mapModel;
}
The mapped tracker there because of how I handle relationships in Hibernate; without it there is an endlessly recursive relationship between parent and child e.g. child.getFather().getFirstChild().getFather().getFirstChild().getFather()...