Même si je souhaite convertir le contenu d'un objet de données en JSON en Java, il existe une référence circulaire ...

Qu'est-ce qu'un "objet de données" dans cet article?

Fait référence à une classe Java définie pour stocker des données. C'est une classe qui n'a que "variable membre privé + getter & setter" ou variable membre public. Je le fais souvent lors de la récupération de données à partir d'une base de données avec une API Web ou JPA. Parfois appelé "DTO".

environnement

Chose que tu veux faire

Même s'il existe un tel objet de données, il y a des moments où vous souhaitez sortir les données stockées ici dans un journal ou autre.

	public class TestDto
	{
		public String stringvar;
		public Integer integervar;
		public int intvar;
		public TestDto2 testDto2;
		public BigDecimal decimal;
		public java.util.Date date;
	}

	public class TestDto2
	{
		public String string2;
		public Integer integer2;
		public int int2;
		public TestDto testDto;
	}

Dans un tel cas, si vous pouvez sortir en JSON comme ↓, ce sera très facile à voir.


{
    "stringvar": "aaa",
    "integervar": 1,
    "intvar": 2,
    "testDto2": {
        "string2": "CCC",
        "integer2": 2,
        "int2": 0,
        "testDto": null
    },
    "decimal": null,
    "date": "2020-08-12T00:00:00.000+0900"
}

Convertir avec reflexionToString

Apache Commons'ToStringBuilder # reflexionToString` a toujours été familier. Il s'agit d'une méthode pratique qui utilise la réflexion pour convertir le contenu d'un objet de données en une chaîne. En fait, vous pouvez spécifier le format de sortie pour le deuxième argument, et JSON est également préparé correctement.

ToStringBuilder.reflectionToString(dto, ToStringStyle.JSON_STYLE)

↓ Résultat de sortie (joliment imprimé séparément)


{
    "stringvar": "aaa",
    "integervar": 1,
    "intvar": 2,
    "testDto2": "jp.example.common.dto.TestDto2@ed17bee",
    "decimal": null,
    "date": "Wed Aug 12 00:00:00 JST 2020"
}

Hmmm, désolé ... Pour les objets de données imbriqués, seuls le nom de la classe et la valeur de hachage tels que " testDto2 ":" jp.example.common.dto.TestDto2@ed17bee ", sont affichés et les données ne peuvent pas être affichées. ToStringBuilder # reflexionToString utilise toString () pour convertir le contenu de chaque membre en une chaîne, donc si la classe parente n'est pas spécifiée comme cette fois, ʻObject # toString` est utilisé et ↑ Le résultat de sortie ressemble à ceci.

Créons une classe de base pour DTO

Ensuite, créons une classe de base pour un objet de données comme ↓.

import org.apache.commons.lang3.builder.ToStringBuilder;

public class CommonDto implements Serializable
{
	@Override
	public String toString()
	{
		return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
	}
	

Et assurez-vous que l'objet de données hérite de la classe ↑.

	public class TestDto extends CommonDto
	{
		public String stringvar;
		public Integer integervar;
		public int intvar;
		public TestDto2 testDto2;
		public BigDecimal decimal;
		public java.util.Date date;
	}

	public class TestDto2 extends CommonDto
	{
		public String string2;
		public Integer integer2;
		public int int2;
		public TestDto testDto;
	}

Dans ce cas, vous pouvez sortir en JSON simplement par toString ().

dto.toString();

↓ Résultat de sortie (joliment imprimé séparément)


{
    "stringvar": "aaa",
    "integervar": 1,
    "intvar": 2,
    "testDto2": {
        "string2": "CCC",
        "integer2": 2,
        "int2": 0,
        "testDto": null
    },
    "decimal": null,
    "date": "Wed Aug 12 00:00:00 JST 2020"
}

Attention aux références circulaires!

On a l'impression que c'est parfait, mais il y a un piège. Lorsqu'un membre d'un objet de données fait référence à son parent. Si vous suivez docilement la référence, vous pourrez vous y référer pour toujours.

En fait, si vous essayez de faire référence à testDto2.testDto, qui est nul dans l'exemple ci-dessus, en tant que parent ...


{
    "stringvar": "aaa",
    "integervar": 1,
    "intvar": 2,
    "testDto2": {
        "string2": "CCC",
        "integer2": 2,
        "int2": 0,
        "testDto": {
            "stringvar": "aaa",
            "integervar": 1,
            "intvar": 2,
            "testDto2": {
                "string2": "CCC",
                "integer2": 2,
                "int2": 0,
                "testDto": {
                    "stringvar": "aaa",
                    "integervar": 1,
                    "intvar": 2,
                    "testDto2": {
                        "string2": "CCC",
                        "integer2": 2,
                        "int2": 0,
                        "testDto": {
            :

Il boucle infiniment comme ça. Je ne pense pas que vous y feriez normalement référence comme ceci, mais si vous créez une telle entité avec JPA et que vous la reliez les uns aux autres, elle renverra exactement un tel objet. Même si vous regardez Stack Overflow, vous pouvez voir certaines personnes qui rencontrent des problèmes avec ce problème. Cela ressemble à un écueil qui est étonnamment facile à rencontrer.

Prend également en charge les références circulaires

Puisque le but de ce temps est la sortie du journal, je voudrais le rendre utilisable sans me soucier de savoir s'il s'agit d'une référence circulaire ou non.

(Par exemple, si le but est une réponse API REST, je ne pense pas que l'objet obtenu de JPA sera répondu tel quel, donc je ne pense pas que vous ayez à vous soucier du problème de référence circulaire.)

Tant que j'utilise ToStringStyle.JSON_STYLE, je pensais que je ne pouvais pas gérer les références circulaires, donc cette fois je vais essayer de créer une classe originale qui est très similaire à ToStringStyle.JSON_STYLE.

La substance de ToStringStyle.JSON_STYLE est JsonToStringStyle, qui est défini comme la classe interne de ToStringStyle, donc copiez-le d'abord dans son intégralité et enregistrez-le dans votre projet. ↓ Comme ça (identique à JsonToStringStyle sauf pour le nom de la classe)

    public class OriginalJsonToStringStyle extends ToStringStyle {

        private static final long serialVersionUID = 1L;

        private static final String FIELD_NAME_QUOTE = "\"";

        /**
         * <p>
         * Constructor.
         * </p>
         *
         * <p>
         * Use the static constant rather than instantiating.
         * </p>
         */
        OriginalJsonToStringStyle() {
            super();

            this.setUseClassName(false);
            this.setUseIdentityHashCode(false);
  :

Ensuite, seule la méthode ↓ réécrit le contenu. En fait, ToStringStyle a à l'origine un mécanisme pour gérer correctement les références circulaires, mais le traitement par la méthode de ↓ est un peu compliqué, et ce traitement est passé. À cause de cela, c'était dans une boucle infinie.

        @Override
        protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {

            if (value == null) {
                appendNullText(buffer, fieldName);
                return;
            }

            if (value instanceof String || value instanceof Character) {
                appendValueAsString(buffer, value.toString());
                return;
            }

            if (value instanceof Number || value instanceof Boolean) {
                buffer.append(value);
                return;
            }

            final String valueAsString = value.toString();
            if (isJsonObject(valueAsString) || isJsonArray(valueAsString)) {
                buffer.append(value);
                return;
            }

            appendDetail(buffer, fieldName, valueAsString);
        }

↓ je l'ai changé comme ça

        @Override
        protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {

            if (value == null) {
                appendNullText(buffer, fieldName);
                return;
            }

            if (value instanceof String || value instanceof Character) {
                appendValueAsString(buffer, value.toString());
                return;
            }

            if(value instanceof java.util.Date)
            {
            	appendValueAsDate(buffer, (java.util.Date)value);
            	return;
            }

            buffer.append(value);
        }

        //Au fait java.util.Date de sortie au format étendu ISO8601
        protected void appendValueAsDate(StringBuffer buffer, java.util.Date value) {
        	DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
        	buffer.append("\"" + df.format(value) + "\"");
        }

Maintenant que nous avons un bon sens des références circulaires, nous allons ajouter la méthode suivante: Dans le cas d'un objet référencé de manière circulaire, la méthode de stringification normale réécrite dans ↑ n'est pas utilisée, et elle est traitée par cette méthode. Le contenu de sortie peut être n'importe quoi, donc le résultat de ʻObjectUtils # identityToString` est le même que la méthode source de substitution. Cependant, des guillemets doubles sont ajoutés avant et après afin de ne pas casser le format JSON.

        @Override
        protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) {
        	buffer.append(FIELD_NAME_QUOTE);
            ObjectUtils.identityToString(buffer, value);
        	buffer.append(FIELD_NAME_QUOTE);
         }

↓ Résultat de sortie (joliment imprimé séparément)

{
    "stringvar": "aaa",
    "integervar": 1,
    "intvar": 2,
    "testDto2": {
        "int2": 0,
        "integer2": 2,
        "string2": "CCC",
        "testDto": {
            "stringvar": "aaa",
            "integervar": 1,
            "intvar": 2,
            "date": "2020-08-12T00:00:00.000+0900",
            "decimal": null,
            "testDto2": "jp.example.common.dto.TestDto2@5577140b"
        }
    }
    "date": "2020-08-12T00:00:00.000+0900",
    "decimal": null
}

Bonnes vibrations! La partie référencée circulairement est sortie avec une certaine duplication, mais elle a été remplacée par la notation " jp.example.common.dto.TestDto2@5577140b ", et elle n'est pas tombée dans une boucle infinie!

Résumé

La conversion d'un objet de données en JSON est possible avec ToStringBuilder # reflexionToString. Cependant, si l'objet est référencé de manière circulaire, comme lors de l'utilisation de JPA, il était nécessaire de créer une classe de traitement spéciale.

En parlant de conversion JSON, il y a GSON et Jackson (ObjectMapper) en plus de ToStringBuilder, mais les objets référencés circulairement sont toujours inutiles.

Recommended Posts

Même si je souhaite convertir le contenu d'un objet de données en JSON en Java, il existe une référence circulaire ...
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3
[Java] Je veux effectuer distinctement avec la clé dans l'objet
[Rails] Je souhaite envoyer des données de différents modèles dans un formulaire
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3 (édition PowerMockito)
Je veux var_dump le contenu de l'intention
Même en Java, je veux sortir true avec un == 1 && a == 2 && a == 3 (deuxième décoction Javassist)
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3 (Black Magic)
[Rails] Je souhaite afficher la destination du lien de link_to dans un onglet séparé
Même en Java, je veux sortir vrai avec un == 1 && a == 2 && a == 3 (magie grise qui n'est pas tant que magie noire)
Même en Java, je veux sortir true avec un == 1 && a == 2 && a == 3 (Royal road edition qui n'est ni magique ni rien)
Je veux trouver la somme de contrôle MD5 d'un fichier en Java et obtenir le résultat sous forme de chaîne de caractères en notation hexadécimale.
[Java] Je souhaite convertir un tableau d'octets en un nombre hexadécimal
J'ai essayé de convertir une chaîne de caractères en un type LocalDate en Java
Je veux créer un fichier Parquet même en Ruby
J'ai créé un client RESAS-API en Java
Je souhaite simplifier l'instruction if-else de la branche conditionnelle en Java
Je veux savoir quelle version de java le fichier jar que j'ai est disponible
Je veux FlashAttribute au printemps même si j'ai défini un proxy inverse! (ne pas faire)
Je veux obtenir une liste du contenu d'un fichier zip et sa taille non compressée
Je souhaite échanger des données JSON (objets) avec Ajax entre Java et JavaScript! ~ Édition de printemps ~
Même si j'écris le paramètre de STRICT_QUOTE_ESCAPING dans CATALINA_OPTS dans tomcat8.5, il n'est pas reflété.
Je veux recréer le contenu des actifs à partir de zéro dans l'environnement construit avec capistrano
Si hash [: a] [: b] [: c] = 0 dans Ruby, je veux que vous étendiez récursivement même si la clé n'existe pas
[Swift] Lorsque vous voulez savoir si le nombre de caractères dans String correspond à un certain nombre ...
À propos de la méthode de conversion d'une chaîne de caractères en entier / fraction (données de conversion) en Java
La milliseconde définie dans /lib/calendars.properties de Java jre est UTC
Je veux rendre le cadre de la zone de texte rouge lorsqu'il y a une erreur de saisie
Fonction statique pour vérifier si l'erreur RVB de BufferdImage est dans le rapport spécifié en Java
Comment vérifier le contenu de la chaîne de caractères java de longueur fixe
Je veux changer la valeur de l'attribut dans Selenium of Ruby
[Pour les débutants] Je souhaite saisir automatiquement des données pré-enregistrées dans le formulaire de saisie avec une commande de sélection.
[Java] Affiche le résultat de ffprobe -show_streams dans JSON et mappe-le à un objet dans Jackson
J'ai essayé de créer une classe parent d'objet de valeur dans Ruby
L'histoire de l'oubli de fermer un fichier en Java et de l'échec
Mémo: [Java] Si un fichier se trouve dans le répertoire surveillé, traitez-le.
Je souhaite obtenir l'adresse IP lors de la connexion au Wi-Fi avec Java
Comment obtenir le chemin absolu d'un répertoire s'exécutant en Java
Je veux ForEach un tableau avec une expression Lambda en Java
Je veux obtenir le nom de champ du champ [Java]. (Vieux ton d'histoire)
Développement Android, comment vérifier null dans la valeur de l'objet JSON
[Rails] Je veux tout réinitialiser car les données de l'environnement local sont incorrectes! Que faire avant ça
Si vous souhaitez satisfaire la couverture de test des méthodes privées dans JUnit
J'ai reçu les données du voyage (application agenda) en Java et j'ai essayé de les visualiser # 001
Faire correspondre le JSON du cas du serpent au champ du cas du chameau en Java (JVM)
[Java] Est-il inutile de vérifier "l'identité" dans l'implémentation de la méthode equals ()?
Comment créer une combinaison unique de données dans la table intermédiaire des rails
J'ai réussi à obtenir un blanc lorsque j'ai apporté le contenu de Beans dans la zone de texte
Je veux voir le contenu de Request sans dire quatre ou cinq
Je veux obtenir récursivement la superclasse et l'interface d'une certaine classe
Je souhaite envoyer un e-mail en Java.
Code pour échapper aux chaînes JSON en Java
Ressentez le passage du temps même à Java
Je voulais que (a == 1 && a == 2 && a == 3) vrai en Java
rsync4j --Je veux toucher rsync en Java.
Je veux être finalement même à kotlin