我想导航到对象的第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 ......)可以做到这一点,你能给我一些关于如何自己写这个的有启发性的想法吗?
答案 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.Class
,java.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将代码作为数据处理,您可以忽略所有正常的对象访问限制。我只使用这种反光深层复制材料进行测试:
在检查深层对象图相等的不同类型对象的测试中
在分析对象图形大小和其他属性的测试中
答案 2 :(得分:4)
一种干净的方法是使用访问者模式将您的编码实现与业务对象分开。有些人会争辩说,您可以简单地将Externalizable
界面与readExternal
/ writeExternal
一起实施,但这会遇到以下问题:
示例强>
/**
* 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();
}
}