web-dev-qa-db-fra.com

Dumping des propriétés d'un objet Java

Existe-t-il une bibliothèque qui videra/imprimera récursivement les propriétés d’un objet? Je cherche quelque chose de similaire à la fonction console.dir () dans Firebug.

Je connais le paramètre commons-lang ReflectionToStringBuilder , mais il ne fait pas de récurrence dans un objet. C'est-à-dire si je lance ce qui suit:

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";
    }
}

Je reçois:

ToString $ Outer @ 1b67f74 [ intValue = 5
innerValue=ToString$Inner@530daa ]

Je réalise que dans mon exemple, j'aurais pu remplacer la méthode toString () pour Inner, mais dans le monde réel, je traite avec des objets externes que je ne peux pas modifier.

63
Kevin

Vous pouvez essayer 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>

Vous pouvez aussi sortir dans JSON

Et faites attention aux références circulaires;)

41
cherouvim

J’ai essayé d’utiliser XStream comme suggéré à l’origine, mais le graphe d’objet que je voulais vider incluait une référence au marshaller XStream lui-même, ce à quoi il n’a pas pris trop de bonté (pourquoi il doit lever une exception plutôt que de l’ignorer ou l'enregistrement d'un avertissement de Nice, je ne suis pas sûr.)

J'ai ensuite essayé le code de user519500 ci-dessus, mais j'ai constaté qu'il me fallait quelques ajustements. Voici une classe que vous pouvez intégrer à un projet offrant les fonctionnalités supplémentaires suivantes:

  • Peut contrôler la profondeur de récursion maximale
  • Peut limiter la sortie des éléments du tableau
  • Peut ignorer toute liste de classes, de champs ou de combinaisons classe + champ - il suffit de passer un tableau avec toute combinaison de noms de classes, paires nom de classe + nom de champ séparées par un signe deux-points ou des noms de champs avec un préfixe de deux-points, par exemple: [<classname>][:<fieldname>]
  • Ne génère pas le même objet deux fois (la sortie indique le moment où un objet a été visité et fournit le code de hachage pour la corrélation) - ceci évite les références circulaires qui posent problème

Vous pouvez appeler cela en utilisant l’une des méthodes ci-dessous:

    String dump = Dumper.dump(myObject);
    String dump = Dumper.dump(myObject, maxDepth, maxArrayElements, ignoreList);

Comme mentionné ci-dessus, vous devez vous méfier des débordements de pile, utilisez donc la fonction de profondeur de récursion maximale pour minimiser les risques.

Espérons que quelqu'un trouvera cela utile!

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;
    }
}
37
John Rix

Vous pouvez utiliser ReflectionToStringBuilder avec un ToStringStyle personnalisé, par exemple:

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

Et puis vous l'invoquez comme ceci:

ReflectionToStringBuilder.toString(value, MyStyle.getInstance());

Attention toutefois aux références circulaires!


Vous pouvez également utiliser json-lib ( http://json-lib.sourceforge.net ) et simplement:

JSONObject.fromObject(value);
20
Anonymous

cela affichera tous les champs (y compris les tableaux d'objets) d'un objet.

Version fixe du message de Ben Williams à partir de ce fil de discussion

Remarque: cette méthode utilise la récursivité. Ainsi, si vous avez un graphe d'objet très profond, vous risquez un débordement de pile (aucun jeu de mots;; IF); vous devez donc utiliser le paramètre VM -Xss10m. Si vous utilisez Eclipse, placez-le dans la boîte de dialogue Augmenter> Exécuter> Configurer> et augmentez (onglet) VM et cliquez sur Appliquer.

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

Je voulais une solution élégante à ce problème qui:

  • N'utilise aucune bibliothèque externe
  • Utilisations Réflexion pour accéder aux champs, y compris les champs de superclasse
  • Utilise la récursivité pour parcourir le graphe d'objets avec un seul cadre de pile par appel
  • Utilise un IdentityHashMap pour gérer les références en arrière et éviter une récursion infinie
  • Gère les primitives, l'auto-boxing, les CharSequences, les enums et les nulls de manière appropriée
  • Vous permet de choisir d'analyser ou non les champs statiques
  • Est assez simple pour modifier selon les préférences de formatage

J'ai écrit la classe d'utilitaire suivante:

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

Je l'ai testé sur un certain nombre de classes et pour moi, il est extrêmement efficace. Par exemple, essayez de l'utiliser pour vider le thread principal:

public static void main(String[] args) throws Exception {
    System.out.println(dump(Thread.currentThread()));
}

Modifier

Depuis que j'ai écrit cet article, j'ai eu raison de créer une version itérative de cet algorithme. La version récursive est limitée en profondeur par le nombre total d'images de pile, mais vous pouvez avoir une raison de vider un graphe d'objet extrêmement volumineux. Pour gérer ma situation, j'ai révisé l'algorithme pour utiliser une structure de données de pile à la place de la pile d'exécution. Cette version est efficace dans le temps et est limitée par la taille du tas au lieu de la profondeur du cadre de la pile.

Vous pouvez télécharger et utiliser la version itérative ici .

6
Bryan W. Wagner

Vous pourriez peut-être utiliser un cadre de liaison XML tel que XStream , Digester ou JAXB pour cela.

4
Fabian Steeg

Vous devriez utiliser RecursiveToStringStyle:

System.out.println(ReflectionToStringBuilder.toString(new Outer(), new RecursiveToStringStyle()));
4
Eduardo

Vous pouvez utiliser Gson pour représenter votre objet au format json:

new GsonBuilder().setPrettyPrinting().create().toJson(yourObject);
1
Ofek Ron

Je vous recommande d'utiliser le GSON Lib pour Java.

si vous utilisez Maven, vous pouvez utiliser this .

Ou vous pouvez télécharger le fichier Jar à partir de ici .

Voici un exemple d'utilisation:

Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(obj);
System.out.println(json);
0
user2110287
JSONObject.fromObject(value)

Ne fonctionne pas pour les objets de la carte avec des clés autres que String. Peut-être que JsonConfig peut gérer cela.

0
Michal Moravcik