Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to recursively serialize an object using reflection?

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?

like image 495
Sawyer Avatar asked Apr 12 '10 15:04

Sawyer


People also ask

What is reflection serialization?

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.

What is reflection in Java serialization?

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.

How do you serialize an object class?

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.


3 Answers

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.

like image 183
BalusC Avatar answered Oct 25 '22 03:10

BalusC


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:

  1. In a test that checked different kinds of objects for deep object graph equality

  2. In a test that analyzed object graph size and other properties

like image 23
mkorpela Avatar answered Oct 25 '22 02:10

mkorpela


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:

  • Your protocol is embededded within your business object, meaning it is distributed across your codebase rather than being in one place.
  • Your application cannot support multiple protocols (as offered by 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 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();
  }
}
like image 41
Adamski Avatar answered Oct 25 '22 01:10

Adamski