It is possible to make a generic serialize method? In which I can pass any object and serialize it acording to it's class? Something like this:
public void serializeObject(T Object) {
try (ObjectOutputStream clientOutputStream = new ObjectOutputStream(socketConnection.getOutputStream());) {
clientOutputStream.writeObject(Object);
System.out.println(user.getUsername());
//clientOutputStream.close();
} catch (Exception e) {
System.out.println(e);
}
}
You could build something like this using reflection. You inspect the object to dump; retrieve all its fields; and then try to serialize those. Of course, that will turn out to be hard, because you have to detect "cycles", to avoid serializing A pointing to B pointing to C pointing back to A for example.
And you would have to understand all the subtle issues, like having inner objects and whatnot. Seriously: this is hard.
Then: serialization isn't the even real problem in this challenge!
The problem is to rebuild objects of generic classes from the serialized data.
So, the real answer is: this is an advanced task where you can easily waste many hours of time. Meaning: you rather step back and clarify your requirements. Instead of re-inventing the wheel, you should use your energy to find an existing framework that matches your needs. Start reading here.
Try out-of-the-box mappers, e.g. Jackson JSON mapper
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
String jsonInString = mapper.writeValueAsString(yourObject);
YourClass yourObject = mapper.readValue(jsonInString, YourClass.class);
It is not possible to create Generic Serializer. Instead you can convert your object to common object that supports serialization.
For example, You can convert your object to String using GSON and serialize the string. During deserialization you pass the deserialized string object to gson to get your object back.
public <T> void serializeObject(T object) throws Exception{
Gson gson = new Gson();
String stringToSerialize = gson.toJson(object).toString();
try(ObjectOutputStream oStream = new ObjectOutputStream(new FileOutputStream("generic.ser"))){
oStream.writeObject(stringToSerialize);
}
}
public <T> T deSerializeObject(Class className) throws Exception{
Gson gson = new Gson();
try(ObjectInputStream iStream = new ObjectInputStream(new FileInputStream("generic.ser"))){
String object = (String)iStream.readObject();
return (T) gson.fromJson(object,className);
}
}
Ofcourse this solution comes with performance degrade. It completely depends on our requirement.
Related
I have an abstract class for configuration files, which can be extended by lots of other classes. I managed to get the system working for writing it to JSON, but now I need the load function.
Here's the general Configuration class:
public class Configuration {
public boolean load(){
FileReader reader = new FileReader(this.getClass().getSimpleName() + ".json");
Gson gson = new Gson();
gson.fromJson(reader, this.getClass());
reader.close();
/** Doesn't give an error, but doesn't set any info to the child class */
}
public boolean save(){
FileWriter writer = new FileWriter(this.getClass().getSimpleName() + ".json");
Gson gson = new Gson();
gson.toJson(this, writer);
writer.close();
/** This all works fine. */
}
}
Here's an example of an extending class:
public class ExampleConfig extends Configuration {
private static transient ExampleConfig i = new ExampleConfig();
public static ExampleConfig get() { return i; }
#Expose public String ServerID = UUID.randomUUID().toString();
}
In my main class I would do:
ExampleConfig.get().load();
System.out.println(ExampleConfig.get().ServerID);
This does not give any errors, but neither is the class loaded from the JSON. It keeps outputting a random UUID even though I want to load one from the JSON file. I'm probably getting the wrong instance of the child class, but I'm out of ideas on how to fix this. (Using this in gson.fromJson(.....); does not work.
You're missing to assign a read value to your configuration instance. Java cannot support anything like this = gson.fromJson(...), and Gson can only return new values and cannot patch existing ones. The below is a sort of Gson hack, and please only use it if it's really a must for you. Again, I would strongly recommend you to redesign your code and separate your configuration objects and configuration readers/writers -- these are just two different things that conflict from the technical perspective. As a result of refactoring, you could have, let's say, once you get an instance of your configuration, just delegate it to a writer to persist it elsewhere. If you need it back, then just get an instance of a reader, read the configuration value and assign it to your configuration (configurations are singletons, I remember), like:
final ConfigurationWriter writer = getConfigurationWriter();
writer.write(ExampleConfig.get());
...
final ConfigurationReader reader = getConfigurationReader();
ExampleConfig.set(reader.read(ExampleConfig.class));
At least this code does not mix two different things, and makes the result of reader.read be explicitly read and assigned to your configuration singleton.
If you're fine to open the gate of evil and make your code work because of hacks, then you could use Gson TypeAdapterFactory in order to cheat Gson and patch the current configuration instance.
abstract class Configuration {
private static final Gson saveGson = new Gson();
public final void load()
throws IOException {
try ( final FileReader reader = new FileReader(getTargetName()) ) {
// You have to instantiate Gson every time (unless you use caching strategies) in order to let it be *specifically* be aware of the current
// Configuration instance class. Thus you cannot make it a static field.
final Gson loadGson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
// A Gson way to denote a type since Configuration.class may not be enough and it also works with generics
private final TypeToken<Configuration> configurationTypeToken = new TypeToken<Configuration>() {
};
#Override
#SuppressWarnings("deprecation") // isAssignableFrom is deprecated
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Checking if the type token represents a parent class for the given configuration
// If yes, then we cheat...
if ( configurationTypeToken.isAssignableFrom(typeToken) ) {
// The map that's artificially bound as great cheating to a current configuration instance
final Map<Type, InstanceCreator<?>> instanceCreators = bindInstance(typeToken.getType(), Configuration.this);
// A factory used by Gson internally, we're intruding into its heart
final ConstructorConstructor constructorConstructor = new ConstructorConstructor(instanceCreators);
final TypeAdapterFactory delegatedTypeAdapterFactory = new ReflectiveTypeAdapterFactory(
constructorConstructor,
gson.fieldNamingStrategy(),
gson.excluder(),
new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor)
);
// Since the only thing necessary here is to define how to instantiate an object
// (and we just give it an already-existing instance)
// ... just delegate the job to Gson -- it would think as if it's creating a new instance.
// Actually it won't create one, but would "patch" the current instance
return delegatedTypeAdapterFactory.create(gson, typeToken);
}
// Otherwise returning a null means looking up for an existing type adapter from how Gson is configured
return null;
}
})
.create();
// The value is still loaded to nowhere, however.
// The type adapter factory is tightly bound to an existing configuration instance via ConstructorConstructor
// This is actually another code smell...
loadGson.fromJson(reader, getClass());
}
}
public final void save()
throws IOException {
try ( final FileWriter writer = new FileWriter(getTargetName()) ) {
saveGson.toJson(this, writer);
}
}
private String getTargetName() {
return getClass().getSimpleName() + ".json";
}
private static Map<Type, InstanceCreator<?>> bindInstance(final Type type, final Configuration existingConfiguration) {
return singletonMap(type, new InstanceCreator<Object>() {
#Override
public Object createInstance(final Type t) {
return t.equals(type) ? existingConfiguration : null; // don't know if null is allowed here though
}
});
}
}
I hope that the comments in the code above are exhaustive. As I said above, I doubt that you need it just because of intention to have a bit nicer code. You could argue that java.util.Properties can load and save itself. Yes, that's true, but java.util.Properties is open to iterate over its properties by design and it can always read and write properties from elsewhere to anywhere. Gson uses reflection, a method of peeking the fields under the hood, and this is awesome for well-designed objects. You need some refactoring and separate two concepts: the data and data writer/reader.
Let's say I have a POJO with quite a few fields. I also have a map with a bunch of properties that would map nicely to fields in the POJO. Now I want to apply the properties in the map to my POJO. How can I do this?
Jackson provides method new ObjectMapper().convertValue(), but that creates a fresh instance of the POJO. Do I really have to do something like this?
om = new ObjectMapper();
pojoMap = om.convertValue(pojo, Map.class);
pojoMap.putAll(properties);
pojo = om.convertValue(pojoMap, Pojo.class);
Isn't there an easier way?
As I have no experience with GSON and we also have it lying around here, how would I do that with GSON?
Yes, you can create an ObjectReader that will update an existing instance from the root JSON object rather than instantiating a new one, using the readerForUpdating method of ObjectMapper:
#Test
public void apply_json_to_existing_object() throws Exception {
ExampleRecord record = new ExampleRecord();
ObjectReader reader = mapper.readerForUpdating(record)
.with(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
.with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
reader.readValue("{ firstProperty: 'foo' }");
reader.readValue("{ secondProperty: 'bar' }");
assertThat(record.firstProperty, equalTo("foo"));
assertThat(record.secondProperty, equalTo("bar"));
}
public static class ExampleRecord {
public String firstProperty;
public String secondProperty;
}
You can also create a value-updating reader from an existing ObjectReader. The following declaration seems equivalent:
ObjectReader reader = mapper.reader(ExampleRecord.class)
.withValueToUpdate(record)
.with(/* features etc */);
Addition
The above didn't actually answer your question, though.
Since you don't have the changes you want to make to the record as JSON, but rather as a map, you have to finagle things so that Jackson will read your Map. Which you can't do directly, but you can write the "JSON" out to a token buffer and then read it back:
#Test
public void apply_map_to_existing_object_via_json() throws Exception {
ExampleRecord record = new ExampleRecord();
Map<String, Object> properties = ImmutableMap.of("firstProperty", "foo", "secondProperty", "bar");
TokenBuffer buffer = new TokenBuffer(mapper, false);
mapper.writeValue(buffer, properties);
mapper.readerForUpdating(record).readValue(buffer.asParser());
assertThat(record.firstProperty, equalTo("foo"));
assertThat(record.secondProperty, equalTo("bar"));
}
(btw if this seems laborious, serializing to a token buffer and deserializing again is in fact how ObjectMapper.convertValue is implemented, so it's not a big change in functionality)
Deserializing works fine if I just pass my custom object through
#POST
public Response saveCustomObject(CustomObject data)
{
// Prints correct value
System.out.println(data);
}
However, if it is a property on another object, it just gets the default value of my custom object
#POST
public Response saveCustomObjectWrapper(CustomObjectWrapper data)
{
// Prints incorrect value
System.out.println(data.getCustomObject());
}
My provider is registered and looks like this:
public CustomObject readFrom(Class<CustomObject> type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, String> mm, InputStream in) throws IOException, WebApplicationException
{
try {
return new CustomObject(IOUtils.toString(in));
} catch (Exception ex) {
throw new ProcessingException("Error deserializing a CustomObject.", ex);
}
}
The problem is that the reader for all other objects doesn't do lookup/delegation while unmarshalling. What I mean by that, can be seen in this answer, where one reader looks up another reader based on the type. Assuming the format is JSON, whether you're using MOXy (the default with Glassfish) or Jackson, the result is the same. The reader is smart enough to handle the the JSON by itself, so doesn't need to lookup any other readers.
One solution would be to create another reader for the wrapper class, and do lookup/delegation, as seen in the link above. If you have a lot of these situations, you may can extend the default reader, and override its unmarshalling method, but I would completely advise against this, unless you really know what you're doing.
Another solution, depending on the serializer you're using, is to write JsonDeserializer (for Jackson) or XmlAdapter (for MOXy or Jackson). For Jackson an example would be something like (you can see a better example here)
public class CustomObjectDeserializer extends JsonDeserializer<CustomObject> {
#Override
public CustomObject deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return new CustomObject("Hello World");
}
}
#JsonDeserialize(using = CustomObjectDeserializer.class)
public class CustomObject {
public String message;
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public CustomObject(String message) { this.message = message; }
public CustomObject(){}
}
In which case, there is no need for a custom reader at all. This will handle CustomObjects and objects that have CustomObject as a member. One problem with this is I'm not sure how or if you can get the InputStream. You just need to use the Jackson APIs to parse the JSON.
If you want to use Jackson instead of the default MOXy for glassfish, you can just add the Jackson dependency
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.13</version>
</dependency>
Then register the JacksonFeature, or simply disable MOXy, as mentioned here. If you want to continue using MOXy, I don't know if there is such thing as a class level adapter, so you will still need the reader as well as create a XmlAdapter for class members. It's a bit of a hassle, but that's why I recommend Jackson, for many other reasons, besides this particular use case. You can see an example of an adapter here
Now a lot of this answer is based on the assumption you are using JSON format, as you haven't specified the media type you are using. If it some other format, then I think maybe your only solution is to create another customer reader for the wrapper.
I am using Google's excellent Gson library for doing JSON (de)serialization. I am making use of the GsonBuilder class to create Gson objects with custom serializers for certain classes. At a later point after the code was deployed, I suddenly started getting Heap Overflow errors.
I studied the heap dump and it pointed to the static final object of the GsonBuilder class:
public static final GsonBuilder JSONIFIER_BUILDER = new GsonBuilder().registerTypeAdapter(Article.class, new ArticleSerializer());
To create a Gson object from the builder, I have a method which is being used by multiple apps in my code.
public String serialize(Object contextObject) {
Gson jsonifier = JSONIFIER_BUILDER.registerTypeAdapter(Config.class, new ConfigSerializer(contextObject)).create();
return jsonifier.toJson(this);
}
From looking at the heap dump I could only infer that the JSONIFIER_BUILDER somehow has reference to all the instances of Gson object it creates.
Can someone tell me if I am making a correct conclusion? If so, how could I have avoided this problem? I have currently made changes to the code so that the serializer method could look like this:
public String serialize(Object contextObject) {
Gson jsonifier = new GsonBuilder()
.registerTypeAdapter(Article.class, new ArticleSerializer()).registerTypeAdapter(Config.class, new ConfigSerializer(contextObject)).create();
return jsonifier.toJson(this);
}
public String serialize(Object contextObject) {
Gson jsonifier = JSONIFIER_BUILDER.registerTypeAdapter(Config.class, new ConfigSerializer(contextObject)).create();
return jsonifier.toJson(this);
}
You keep registering new types to your static builder. Which it apparently does not overwrite the previous one.
GsonBuilder source
I have a slightly odd question. I have created an object, let's call it a Profile, that successfully parses single JSON objects via an API that I call. There is also a multi-profile interface that will return a JSON array of Profile objects. The problem is, the multi-profile interface turns the sub objects into strings. Is there an automatic way I can tell jackson to parse these into objects?
Example of a single object:
{ "foo": "bar" }
Example of a multi object:
[ "{ \"foo\": \"bar\" }", "{ \"blah\": \"ugh\" }" ]
(Sorry can't use real data)
Notice that the sub objects are actually strings, with escaped quotes inside them.
For completeness, my code for the multi object parse looks like this:
ObjectMapper mapper = new ObjectMapper();
Profile[] profile_array = mapper.readValue(response.content, Profile[].class);
for (Profile p: profile_array)
{
String user = p.did;
profiles.put(user, p);
}
As I said, in the single-profile case, the Profile object parses. In the multi-profile case, I get this exception:
Exception: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.xyz.id.profile.Profile, problem: no suitable creator method found to deserialize from JSON String
I suppose you'll have to create a custom deserializer and apply it to the every element of that array.
class MyCustomDeserializer extends JsonDeserializer<Profile> {
private static ObjectMapper om = new ObjectMapper();
#Override
public Profile deserialize(JsonParser jp, DeserializationContext ctxt) {
// this method is responsible for changing a single text node:
// "{ \"foo\": \"bar\" }"
// Into a Profile object
return om.readValue(jp.getText(), Profile.class);
}
}
There is no out-of-the-box support for "re-parsing" of embedded JSON-in-JSON content.
But this sounds like a possible request for enhancement (RFE)...
Have you tried using JAXB?
final ObjectMapper mapper = new ObjectMapper();
// Setting up support of JAXB
final AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
// make deserializer use JAXB annotations (only)
mapper.getDeserializationConfig().setAnnotationIntrospector(
introspector);
// make serializer use JAXB annotations (only)
mapper.getSerializationConfig().setAnnotationIntrospector(
introspector);
final StringReader stringReader = new StringReader(response);
respGetClasses = mapper.readValue(stringReader,
FooBarClass.class);
The above should get you started...
Also, you would need to mark each subclass like so:
#XmlElement(name = "event")
public List<Event> getEvents()
{
return this.events;
}