I want to navigate to the N-th level of an object, and serialize it's properties in String format. For Example:
class Animal {
public String name;
public int weight;
public Animal friend;
public Set<Animal> children = new HashSet<Animal>() ;
}
should be serialized like this:
{name:"Monkey",
weight:200,
friend:{name:"Monkey Friend",weight:300 ,children:{...if has children}},
children:{name:"MonkeyChild1",weight:100,children:{... recursively nested}}
}
And you may probably notice that it is similar to serializing an object to json. I know there're many libs(Gson,Jackson...) can do this, can you give me some instructive ideas on how to write this by myself?
Implicit Use of Reflection The Java Serialization framework provides the capability of taking an object and creating a byte representation that can be used to restore the object at a later point in time. This serialization framework uses the reflection mechanism to serialize objects.
Java serialization uses reflection to scrape all necessary data from the object's fields, including private and final fields. If a field contains an object, that object is serialized recursively. Even though you might have getters and setters, these functions are not used when serializing an object in Java.
To serialize an object means to convert its state to a byte stream so that the byte stream can be reverted back into a copy of the object. A Java object is serializable if its class or any of its superclasses implements either the java. io. Serializable interface or its subinterface, java.
Google Gson can do this particular task in a single line:
String json = new Gson().toJson(animal);
The other way round is by the way also as easy:
Animal animal = new Gson().fromJson(json, Animal.class);
I haven't seen another JSON serializer yet with better support for generics, collections/maps and (nested) javabeans.
Update: To the point, you just need to learn about reflection API. I recommend to get yourself through the Sun tutorial on the subject first. In a nutshell, you can use Object#getClass()
and all the methods provided by java.lang.Class
, java.lang.reflect.Method
, etc to determine the one and other. Google Gson is open source, take your benefit of it as well.
Serialization is basically deep cloning.
You need to track each object reference for reoccurrencies (for example by using IdentityHashMap). What ever is your final implementation method (if not external library) remember to check for object reoccurrencies or you may end up in an infinent loop (when object A has reference to object B that again has reference to object A or something more complex loop in an object graph).
One way is to traverse through object graph with DFS-like algorithm and build the clone (serialized string) from there.
This pseudo-code hopefully explains how:
visited = {}
function visit(node) {
if node in visited {
doStuffOnReoccurence(node)
return
}
visited.add(node)
doStuffBeforeOthers(node)
for each otherNode in node.expand() visit(otherNode)
doStuffAfterOthers(node)
}
The visited set in the example is where I would use identity set (if there was one) or IdentityHashMap.
When finding out fields reflectively (thats the node.expand() part) remember to go through superclass fields also.
Reflection should not be used in a "normal" development case. Reflection handles code as a data and you can ignore all normal object access restrictions. I've used this reflective deep copy stuff only for tests:
In a test that checked different kinds of objects for deep object graph equality
In a test that analyzed object graph size and other properties
A clean way to approach this is to use a visitor pattern to keep your encoding implementation separate from your business objects. Some people will argue that you can simply implement the Externalizable
interface along with readExternal
/ writeExternal
but this has the problems that:
Example
/**
* Our visitor definition. Includes a visit method for each
* object it is capable of encoding.
*/
public interface Encoder {
void visitAnimal(Animal a);
void visitVegetable(Vegetable v);
void visitMineral(Mineral m);
}
/**
* Interface to be implemented by each class that can be encoded.
*/
public interface Encodable {
void applyEncoder(Encoder e);
}
public class Animal implements Encodable {
public void applyEncoder(Encoder e) {
// Make call back to encoder to encode this particular Animal.
// Different encoder implementations can be passed to an Animal
// *without* it caring.
e.visitAnimal(this);
}
}
Typically one would then define a stateful Encoder
implementation that would "push" each object to an OutputStream
when its visitXXX
method is called; e.g.
public class EncoderImpl implements Encoder {
private final DataOutputStream daos;
public EncoderImpl(File file) throws IOException {
this.daos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
}
public void visitAnimal(Animal a) {
daos.writeInt(a.getWeight());
daos.writeUTF(a.getName());
// Write the number of children followed by an encoding of each child animal.
// This allows for easy decoding.
daos.writeInt(a.getChildren().size());
for (Animal child : a.getChildren()) {
visitAnimal(child);
}
}
// TODO: Implement other visitXXX methods.
/**
* Called after visiting each object that requires serializing.
*/
public void done() {
daos.close();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With