Dumping propriedades de um objeto Java
-
03-07-2019 - |
Pergunta
Existe uma biblioteca que irá recursivamente despejar / imprimir uma objetos propriedades? Eu estou procurando algo semelhante ao console.dir () função no Firebug.
Estou ciente do commons-lang ReflectionToStringBuilder mas não recurse em um objeto. Ou seja, se eu executar o seguinte:
public class ToString {
public static void main(String [] args) {
System.out.println(ReflectionToStringBuilder.toString(new Outer(), ToStringStyle.MULTI_LINE_STYLE));
}
private static class Outer {
private int intValue = 5;
private Inner innerValue = new Inner();
}
private static class Inner {
private String stringValue = "foo";
}
}
Eu recebo:
ToString $ Outer @ 1b67f74 [ intValue = 5
innerValue = ToString $ Inner @ 530daa ]
Eu percebo que no meu exemplo, eu poderia ter anulado o método toString () para Inner mas no mundo real, eu estou lidando com objetos externos que não posso modificar.
Solução
Você poderia tentar XStream .
XStream xstream = new XStream(new Sun14ReflectionProvider(
new FieldDictionary(new ImmutableFieldKeySorter())),
new DomDriver("utf-8"));
System.out.println(xstream.toXML(new Outer()));
imprime:
<foo.ToString_-Outer>
<intValue>5</intValue>
<innerValue>
<stringValue>foo</stringValue>
</innerValue>
</foo.ToString_-Outer>
Você também poderia saída na JSON
E ter cuidado com referências circulares;)
Outras dicas
Eu tentei usar XStream como inicialmente sugerido, mas acontece que o gráfico do objeto que eu queria para despejar incluiu uma volta referência à própria marshaller XStream, que não demorou muito gentilmente para (por isso que deve lançar uma exceção em vez de ignorá-lo ou entrar um bom aviso, eu não tenho certeza.)
Então eu tentei o código de user519500 acima, mas descobri que eu precisava de alguns ajustes. Aqui está uma classe que você pode rolar em um projeto que oferece os seguintes recursos extras:
- Pode controlar profundidade de recursão max
- Can elementos da matriz limite de saída
- Pode ignorar qualquer lista de classes, campos ou combinações de classe + campo - basta passar um array com qualquer combinação de nomes de classe, classname + pares fieldName separados com dois pontos, ou fieldnames com dois pontos prefixo ou seja:
[<classname>][:<fieldname>]
- não será emitido o mesmo objeto duas vezes (a saída indica quando um objeto foi visitado anteriormente e fornece o código hash para a correlação) - isso evita referências circulares causando problemas
Você pode chamar isso usando um dos dois métodos a seguir:
String dump = Dumper.dump(myObject);
String dump = Dumper.dump(myObject, maxDepth, maxArrayElements, ignoreList);
Como mencionado acima, você precisa ter cuidado de pilha-transborda com isso, então usar o recurso de profundidade de recursão máximo para minimizar o risco.
Esperemos que alguém vai encontrar este! Útil
package com.mycompany.myproject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Dumper {
private static Dumper instance = new Dumper();
protected static Dumper getInstance() {
return instance;
}
class DumpContext {
int maxDepth = 0;
int maxArrayElements = 0;
int callCount = 0;
HashMap<String, String> ignoreList = new HashMap<String, String>();
HashMap<Object, Integer> visited = new HashMap<Object, Integer>();
}
public static String dump(Object o) {
return dump(o, 0, 0, null);
}
public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {
DumpContext ctx = Dumper.getInstance().new DumpContext();
ctx.maxDepth = maxDepth;
ctx.maxArrayElements = maxArrayElements;
if (ignoreList != null) {
for (int i = 0; i < Array.getLength(ignoreList); i++) {
int colonIdx = ignoreList[i].indexOf(':');
if (colonIdx == -1)
ignoreList[i] = ignoreList[i] + ":";
ctx.ignoreList.put(ignoreList[i], ignoreList[i]);
}
}
return dump(o, ctx);
}
protected static String dump(Object o, DumpContext ctx) {
if (o == null) {
return "<null>";
}
ctx.callCount++;
StringBuffer tabs = new StringBuffer();
for (int k = 0; k < ctx.callCount; k++) {
tabs.append("\t");
}
StringBuffer buffer = new StringBuffer();
Class oClass = o.getClass();
String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);
if (ctx.ignoreList.get(oSimpleName + ":") != null)
return "<Ignored>";
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString().substring(1));
buffer.append("[\n");
int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));
for (int i = 0; i < rowCount; i++) {
buffer.append(tabs.toString());
try {
Object value = Array.get(o, i);
buffer.append(dumpValue(value, ctx));
} catch (Exception e) {
buffer.append(e.getMessage());
}
if (i < Array.getLength(o) - 1)
buffer.append(",");
buffer.append("\n");
}
if (rowCount < Array.getLength(o)) {
buffer.append(tabs.toString());
buffer.append(Array.getLength(o) - rowCount + " more array elements...");
buffer.append("\n");
}
buffer.append(tabs.toString().substring(1));
buffer.append("]");
} else {
buffer.append("\n");
buffer.append(tabs.toString().substring(1));
buffer.append("{\n");
buffer.append(tabs.toString());
buffer.append("hashCode: " + o.hashCode());
buffer.append("\n");
while (oClass != null && oClass != Object.class) {
Field[] fields = oClass.getDeclaredFields();
if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {
if (oClass != o.getClass()) {
buffer.append(tabs.toString().substring(1));
buffer.append(" Inherited from superclass " + oSimpleName + ":\n");
}
for (int i = 0; i < fields.length; i++) {
String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());
String fName = fields[i].getName();
fields[i].setAccessible(true);
buffer.append(tabs.toString());
buffer.append(fName + "(" + fSimpleName + ")");
buffer.append("=");
if (ctx.ignoreList.get(":" + fName) == null &&
ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&
ctx.ignoreList.get(fSimpleName + ":") == null) {
try {
Object value = fields[i].get(o);
buffer.append(dumpValue(value, ctx));
} catch (Exception e) {
buffer.append(e.getMessage());
}
buffer.append("\n");
}
else {
buffer.append("<Ignored>");
buffer.append("\n");
}
}
oClass = oClass.getSuperclass();
oSimpleName = oClass.getSimpleName();
}
else {
oClass = null;
oSimpleName = "";
}
}
buffer.append(tabs.toString().substring(1));
buffer.append("}");
}
ctx.callCount--;
return buffer.toString();
}
protected static String dumpValue(Object value, DumpContext ctx) {
if (value == null) {
return "<null>";
}
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Short.class ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Float.class ||
value.getClass() == java.lang.Byte.class ||
value.getClass() == java.lang.Character.class ||
value.getClass() == java.lang.Double.class ||
value.getClass() == java.lang.Boolean.class ||
value.getClass() == java.util.Date.class ||
value.getClass().isEnum()) {
return value.toString();
} else {
Integer visitedIndex = ctx.visited.get(value);
if (visitedIndex == null) {
ctx.visited.put(value, ctx.callCount);
if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {
return dump(value, ctx);
}
else {
return "<Reached max recursion depth>";
}
}
else {
return "<Previously visited - see hashCode " + value.hashCode() + ">";
}
}
}
private static String getSimpleNameWithoutArrayQualifier(Class clazz) {
String simpleName = clazz.getSimpleName();
int indexOfBracket = simpleName.indexOf('[');
if (indexOfBracket != -1)
return simpleName.substring(0, indexOfBracket);
return simpleName;
}
}
Você pode usar ReflectionToStringBuilder com um ToStringStyle personalizado, por exemplo:
class MyStyle extends ToStringStyle {
private final static ToStringStyle instance = new MyStyle();
public MyStyle() {
setArrayContentDetail(true);
setUseShortClassName(true);
setUseClassName(false);
setUseIdentityHashCode(false);
setFieldSeparator(", " + SystemUtils.LINE_SEPARATOR + " ");
}
public static ToStringStyle getInstance() {
return instance;
};
@Override
public void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (!value.getClass().getName().startsWith("java")) {
buffer.append(ReflectionToStringBuilder.toString(value, instance));
} else {
super.appendDetail(buffer, fieldName, value);
}
}
@Override
public void appendDetail(StringBuffer buffer, String fieldName, Collection value) {
appendDetail(buffer, fieldName, value.toArray());
}
}
E então você invocá-lo como:
ReflectionToStringBuilder.toString(value, MyStyle.getInstance());
Cuidado com referências circulares embora!
Você também pode usar JSON-lib ( http://json-lib.sourceforge.net ) e apenas fazer:
JSONObject.fromObject(value);
este irá imprimir todos os campos (incluindo matrizes de objetos) de um objeto.
versão fixa do Ben Williams post de esta discussão
Nota: este usa o método de recursão de modo Se você tem um objeto gráfico muito profundo, você pode obter uma pilha de estouro (sem trocadilhos;) Se assim você precisa usar o VM parâmetro -Xss10m. Se o seu usando eclipse colocá-lo em funcionamento> runconfiguration> aumenta caixa augment (guia) VM e pressione aplicar
import java.lang.reflect.Array;
import java.lang.reflect.Field;
public static String dump(Object o) {
StringBuffer buffer = new StringBuffer();
Class oClass = o.getClass();
if (oClass.isArray()) {
buffer.append("Array: ");
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++) {
Object value = Array.get(o, i);
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Boolean.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Double.class ||
value.getClass() == java.lang.Short.class ||
value.getClass() == java.lang.Byte.class
) {
buffer.append(value);
if(i != (Array.getLength(o)-1)) buffer.append(",");
} else {
buffer.append(dump(value));
}
}
buffer.append("]\n");
} else {
buffer.append("Class: " + oClass.getName());
buffer.append("{\n");
while (oClass != null) {
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try {
Object value = fields[i].get(o);
if (value != null) {
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Boolean.class ||
value.getClass() == java.lang.Double.class ||
value.getClass() == java.lang.Short.class ||
value.getClass() == java.lang.Byte.class
) {
buffer.append(value);
} else {
buffer.append(dump(value));
}
}
} catch (IllegalAccessException e) {
buffer.append(e.getMessage());
}
buffer.append("\n");
}
oClass = oClass.getSuperclass();
}
buffer.append("}\n");
}
return buffer.toString();
}
Eu queria uma solução elegante para este problema que:
- Não usar qualquer biblioteca externa
- Reflexão para campos de acesso, incluindo superclasse campos
- Usa recursão para atravessar o Object-gráfico com apenas um quadro de pilha por chamada
- IdentityHashMap para trás punho referências e evitar infinita recursão
- alças primitivos, auto-boxing, CharSequences, enums, e nulos adequadamente
- Permite que você escolha se quer ou não analisar campos estáticos
- É bastante simples para modificar de acordo com a formatação preferências
Eu escrevi o seguinte classe de utilitário:
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map.Entry;
import java.util.TreeMap;
/**
* Utility class to dump {@code Object}s to string using reflection and recursion.
*/
public class StringDump {
/**
* Uses reflection and recursion to dump the contents of the given object using a custom, JSON-like notation (but not JSON). Does not format static fields.<p>
* @see #dump(Object, boolean, IdentityHashMap, int)
* @param object the {@code Object} to dump using reflection and recursion
* @return a custom-formatted string representing the internal values of the parsed object
*/
public static String dump(Object object) {
return dump(object, false, new IdentityHashMap<Object, Object>(), 0);
}
/**
* Uses reflection and recursion to dump the contents of the given object using a custom, JSON-like notation (but not JSON).<p>
* Parses all fields of the runtime class including super class fields, which are successively prefixed with "{@code super.}" at each level.<p>
* {@code Number}s, {@code enum}s, and {@code null} references are formatted using the standard {@link String#valueOf()} method.
* {@code CharSequences}s are wrapped with quotes.<p>
* The recursive call invokes only one method on each recursive call, so limit of the object-graph depth is one-to-one with the stack overflow limit.<p>
* Backwards references are tracked using a "visitor map" which is an instance of {@link IdentityHashMap}.
* When an existing object reference is encountered the {@code "sysId"} is printed and the recursion ends.<p>
*
* @param object the {@code Object} to dump using reflection and recursion
* @param isIncludingStatics {@code true} if {@code static} fields should be dumped, {@code false} to skip them
* @return a custom-formatted string representing the internal values of the parsed object
*/
public static String dump(Object object, boolean isIncludingStatics) {
return dump(object, isIncludingStatics, new IdentityHashMap<Object, Object>(), 0);
}
private static String dump(Object object, boolean isIncludingStatics, IdentityHashMap<Object, Object> visitorMap, int tabCount) {
if (object == null ||
object instanceof Number || object instanceof Character || object instanceof Boolean ||
object.getClass().isPrimitive() || object.getClass().isEnum()) {
return String.valueOf(object);
}
StringBuilder builder = new StringBuilder();
int sysId = System.identityHashCode(object);
if (object instanceof CharSequence) {
builder.append("\"").append(object).append("\"");
}
else if (visitorMap.containsKey(object)) {
builder.append("(sysId#").append(sysId).append(")");
}
else {
visitorMap.put(object, object);
StringBuilder tabs = new StringBuilder();
for (int t = 0; t < tabCount; t++) {
tabs.append("\t");
}
if (object.getClass().isArray()) {
builder.append("[").append(object.getClass().getName()).append(":sysId#").append(sysId);
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object arrayObject = Array.get(object, i);
String dump = dump(arrayObject, isIncludingStatics, visitorMap, tabCount + 1);
builder.append("\n\t").append(tabs).append("\"").append(i).append("\":").append(dump);
}
builder.append(length == 0 ? "" : "\n").append(length == 0 ? "" : tabs).append("]");
}
else {
// enumerate the desired fields of the object before accessing
TreeMap<String, Field> fieldMap = new TreeMap<String, Field>(); // can modify this to change or omit the sort order
StringBuilder superPrefix = new StringBuilder();
for (Class<?> clazz = object.getClass(); clazz != null && !clazz.equals(Object.class); clazz = clazz.getSuperclass()) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (isIncludingStatics || !Modifier.isStatic(field.getModifiers())) {
fieldMap.put(superPrefix + field.getName(), field);
}
}
superPrefix.append("super.");
}
builder.append("{").append(object.getClass().getName()).append(":sysId#").append(sysId);
for (Entry<String, Field> entry : fieldMap.entrySet()) {
String name = entry.getKey();
Field field = entry.getValue();
String dump;
try {
boolean wasAccessible = field.isAccessible();
field.setAccessible(true);
Object fieldObject = field.get(object);
field.setAccessible(wasAccessible); // the accessibility flag should be restored to its prior ClassLoader state
dump = dump(fieldObject, isIncludingStatics, visitorMap, tabCount + 1);
}
catch (Throwable e) {
dump = "!" + e.getClass().getName() + ":" + e.getMessage();
}
builder.append("\n\t").append(tabs).append("\"").append(name).append("\":").append(dump);
}
builder.append(fieldMap.isEmpty() ? "" : "\n").append(fieldMap.isEmpty() ? "" : tabs).append("}");
}
}
return builder.toString();
}
}
Eu testei-o em um número de classes e para mim é extremamente eficiente. Por exemplo, tente usá-lo para despejar o segmento principal:
public static void main(String[] args) throws Exception {
System.out.println(dump(Thread.currentThread()));
}
Editar
Desde escrever este post eu tinha razão para criar uma versão interativa do algoritmo. A versão recursiva é limitado em profundidade por quadros de pilha total, mas você pode ter razão para despejar um gráfico de objeto extremamente grande. Para lidar com a minha situação, eu revisto o algoritmo para usar uma estrutura de dados pilha no lugar da pilha de execução. Esta versão é tempo eficiente e é limitado pelo tamanho da pilha em vez de profundidade quadro de pilha.
Você pode baixar e usar a versão iterativo aqui .
Você deve usar RecursiveToStringStyle:
System.out.println(ReflectionToStringBuilder.toString(new Outer(), new RecursiveToStringStyle()));
Você pode usar Gson para representar o objeto no formato JSON:
new GsonBuilder().setPrettyPrinting().create().toJson(yourObject);
JSONObject.fromObject(value)
não funciona para mapear objetos com outras teclas do que cadeia. Talvez JsonConfig pode lidar com isso.