如何使用反射递归序列化对象?

时间:2010-04-12 15:12:58

标签: java json recursion

我想导航到对象的第N级,并以String格式序列化它的属性。 例如:

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

应该像这样序列化:

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

你可能会注意到它类似于将对象序列化为json。我知道有很多图书馆(Gson,Jackson ......)可以做到这一点,你能给我一些关于如何自己写这个的有启发性的想法吗?

3 个答案:

答案 0 :(得分:6)

Google Gson可以在一行中执行此特定任务:

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

另一种方式也顺便说一句:

Animal animal = new Gson().fromJson(json, Animal.class);

我还没有看到另一个JSON序列化程序,它更好地支持泛型,集合/映射和(嵌套)javabeans。

更新:到目前为止,您只需要了解反射API。我建议先让自己完成Sun tutorial on the subject。简而言之,您可以使用Object#getClass()以及java.lang.Classjava.lang.reflect.Method等提供的所有方法来确定其中一个。 Google Gson是开源的,也可以从中受益。

答案 1 :(得分:5)

序列化基本上是深度克隆。

您需要跟踪每个对象参考以获取余额(例如,使用IdentityHashMap)。什么是你的最终实现方法(如果不是外部库)记得检查对象再现,或者你可能最终在一个非常大的循环中(当对象A引用对象B时再次引用对象A或更复杂的循环对象图)。

一种方法是使用类似DFS的算法遍历对象图,并从那里构建克隆(序列化字符串)。

这个伪代码有希望解释如何:

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)
}

示例中的访问集是我将使用标识集(如果有的话)或IdentityHashMap的地方。

当反射地找出字段时(那就是node.expand()部分)也记得要经历超类字段。

不应在“正常”开发案例中使用反射。 Reflection将代码作为数据处理,您可以忽略所有正常的对象访问限制。我只使用这种反光深层复制材料进行测试:

  1. 在检查深层对象图相等的不同类型对象的测试中

  2. 在分析对象图形大小和其他属性的测试中

答案 2 :(得分:4)

一种干净的方法是使用访问者模式将您的编码实现与业务对象分开。有些人会争辩说,您可以简单地将Externalizable界面与readExternal / writeExternal一起实施,但这会遇到以下问题:

  • 您的协议嵌入在您的业务对象中,这意味着它分布在您的代码库中,而不是在一个地方。
  • 您的应用程序无法支持多种协议(由Google协议缓冲区提供)。

示例

/**
 * 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);
  }
}

然后,通常会定义一个有状态Encoder实现,在调用其OutputStream方法时将每个对象“推送”到visitXXX; 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();
  }
}