views:

445

answers:

3

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?

A: 

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

mkorpela
Yeah,you mentioned about IdentityHashMap, I notice that the author of <java reflection in action> also used IdentityHashMap in his example codes.But I don't understand IdentityHashMap, even I've read java documentation about it.
ZZcat
Difference compared to normal map is that it uses identity (==) as key instead of equals. When you are making a deep clone you need to know when something is really the same object..
mkorpela
A: 

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.

BalusC
Thanks, I use Gson a lot and jackson, jsonlib. I want to know how to write this using reflection.
ZZcat
Huh? Why do you want to reinvent the wheel? At any way: Gson is open source. Take your benefit.
BalusC
lol,the father of json said , the good thing about reinvent wheels is that, you can find a round one. yeah, using Gson is stunning simple, but I still don't know how to write this by myself, what if some day I encountered similar problems but there's no libs can use ?
ZZcat
I'm not sure that makes tons of sense, but that's fine. :)But even then, perhaps have a look at how other libs do it? There are even simpler packages; Hessian for example has rather simple reflection-based serializer/deserializer. JDK has bean serializer (forgot its name), and so on. Just go and check how those libs do it.But it all just boils down to using methods from java.lang.Class -- getDeclaredFields / Methods etc (and doing this recursively for super types). And then accessing fields/methods dynamically. Nothing particularly fancy.
StaxMan
I had already checked out Gson source before asking this question. Gson is a full-fledged serializer that can handle most conditions.It's source is kind of over encapsulated, I think for simple problems,this can be done within 100 lines of code.
ZZcat
+2  A: 

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();
  }
}
Adamski