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