How to recursively serialize an object using reflection?

I want to go to the Nth level of an object and serialize its properties in String format. For instance:

class Animal { public String name; public int weight; public Animal friend; public Set<Animal> children = new HashSet<Animal>() ; } 

should be serialized as follows:

 {name:"Monkey", weight:200, friend:{name:"Monkey Friend",weight:300 ,children:{...if has children}}, children:{name:"MonkeyChild1",weight:100,children:{... recursively nested}} } 

And you will probably notice that it is similar to serializing a json object. I know there are many libs (Gson, Jackson ...), can you give me some instructive ideas on how to write this yourself?

+4
source share
3 answers

Serialization is basically deep cloning.

You need to track each link to objects for repetitions (for example, using IdentityHashMap ). Whatever your final implementation method (if not an external library), be sure to check for repeated calls or you may end up in an infinite loop (when object A refers to object B, which again refers to object A, or something more complex in graph objects).

One way is to go through the object graph using a DFS-like algorithm and build a clone there (serialized string).

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 should use the set of identifiers (if any) or IdentityHashMap.

When analyzing fields reflectively (thats node.expand ()), remember to also go through the fields of the superclass.

Reflection should not be used in a "normal" case of development. Reflection treats the code as data, and you can ignore all the usual object access restrictions. I used this deep copy reflective material for tests only:

  • In a test that tested various types of objects to express a deep object graph

  • In a test that analyzes the graph size of an object and other properties

+4
source

Google Gson can accomplish this specific task in one line:

 String json = new Gson().toJson(animal); 

In another way, by the way, it is also 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: Unfortunately, you just need to know about the reflection API. First, I recommend going through the Sun tutorial 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 both. Google Gson is open source, take your advantage too.

+5
source

A clean approach to use is to use a visitor template to keep your encoding implementation separate from your business objects . Some people claim that you can just implement the Externalizable interface along with readExternal / writeExternal , but this has problems that:

  • Your protocol is embedded in your business object, that is, it is distributed across your code base, and is not located in one place.
  • Your application cannot support multiple protocols (as suggested by the Google protocol buffers).

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 could define a state Encoder implementation that "clicked" every object on the OutputStream when its visitXXX method was visitXXX ; eg.

 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(); } } 
+4
source

Source: https://habr.com/ru/post/1306708/


All Articles