[JAVA] Comment gérer les éléments dont vous ne savez pas s'ils sont uniques ou en matrice dans GSON

Histoire

Lorsque j'ai obtenu le JSON avec l'API REST Redmine, la notation suivante est apparue dans "custom_fields".

"custom_fields": [
  {
    "id": 1,
    "name": "Objet 1",
    "value": "a"
  },
  {
    "id": 2,
    "name": "Point 2",
    "multiple": true,
    "value": [
      "b"
    ]
  },
  {
    "id": 3,
    "name": "Point 3",
    "multiple": true,
    "value": [
      "c",
      "d"
    ]
  },

Il semble que si le contenu de «valeur» est «multiple»: vrai », c'est un objet tableau, sinon il est unique. Comme dans l'exemple ci-dessus, il existe 3 modèles.

  1. Élément unique (multiple non spécifié)
  2. Le nombre d'éléments dans le tableau est de un
  3. Le nombre d'éléments dans le tableau est de 2 ou plus

Il existe aussi une méthode pour tout convertir en tableau même s'il est unique au moment de la désérialisation (JSON → Java), mais comme il ne peut pas être restauré à l'expression d'origine au moment de la sérialisation (je ne sais pas s'il s'agit d'un seul ou d'un tableau avec 1 élément), mettez ce contrôle Dans cet esprit, j'ai également envisagé une mise en œuvre à usage général.

la mise en oeuvre

Objet de données

Les objets pour mettre des données au moment de la désérialisation sont les suivants.

public class MultipleType<T> {

	private T value;

	private List<T> values;

	public boolean isMultiple() {
		return values != null;
	}

	//Ci-dessous getter/setter

}

Utilisez une valeur ou des valeurs et rendez l'autre null.

Le "custom_fields" ci-dessus ressemble à ce qui suit.

public class CustomField {
	private String id;
	private String name;
	private boolean multiple;
	private MultipleType<String> value;

	//Ci-dessous getter/setter
}

Je veux utiliser les fonctions de GSON autant que possible, donc j'utiliserai le même nom de champ que JSON.

Sérialiseur / désérialiseur

Implémentez le sérialiseur / désérialiseur personnalisé de GSON comme suit.

public class MultipleTypeAdapter implements JsonDeserializer<MultipleType<?>>, JsonSerializer<MultipleType<?>> {

	@Override
	public MultipleType<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
			throws JsonParseException {

		final MultipleType<?> result = new MultipleType<>();

		if (json.isJsonArray()) {
			result.setValues(deserializeArray(json, typeOfT, context));
		} else {
			result.setValue(context.deserialize(json, getGenericType(typeOfT)));
		}
		return result;
	}

	private <T> List<T> deserializeArray(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
		final List<T> values = new ArrayList<>();
		final Type t = getGenericType(typeOfT);
		for (JsonElement e : json.getAsJsonArray()) {
			values.add(context.deserialize(e, t));
		}
		return values;
	}

	/* get actual Type of <?> */
	private Type getGenericType(Type typeOfT) {
		return ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
	}

	@Override
	public JsonElement serialize(MultipleType<?> src, Type typeOfSrc, JsonSerializationContext context) {
		return context.serialize(src.isMultiple() ? src.getValues() : src.getValue());
	}

}

Il y a trois points principaux.

  1. Au moment de la désérialisation, utilisez ʻisJsonArray () `pour vérifier s'il s'agit d'un tableau et changer le processus.
  2. Obtenez le paramètre de type de MultipleType
  3. Au moment de la sérialisation, utilisez ʻisMultiple () `pour vérifier s'il s'agit d'un tableau et changer le processus.

Surtout 2 est un peu compliqué. Tout d'abord, nous devons savoir ce que représente l'argument Type typeOfT de la méthode deserialize. Il contient un objet qui représente le paramètre de type de l'interface JsonDeserializer. (Pour être exact, le type de l'objet réellement utilisé lors de l'exécution) Les paramètres de type génériques sont perdus au moment de la compilation, vous pouvez donc vous y référer au moment de l'exécution.

La définition nous dit que typeOfT est MultipleType <?>, Mais comme nous avons besoin de connaître la partie<?>, Nous obtenons les paramètres de type internes avec la méthode getGenericType. Je vais.

La raison pour laquelle le type d'exécution du paramètre de type <?> Est nécessaire est que si le type correct est donné par la méthode context.deserialize, la désérialisation ultérieure peut être laissée à la fonction de GSON. .. En particulier, vous n'avez pas besoin d'écrire à la main une désérialisation de structure complexe où les champs de l'objet de données sont de type non primitif (y compris String).

Où GSON est utilisé

L'utilisation de GSON est la suivante.

final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MultipleType.class, new MultipleTypeAdapter());
final Gson gson = gsonBuilder.create();

Puisque la classe MultipleTypeAdapter n'est pas un type générique, c'est une erreur qu'il n'y a pas d'avertissement. C'est pourquoi nous avons utilisé <?> (Wildcards) pour les paramètres de type désérialiseur / sérialiseur personnalisé. (Je ne peux pas expliquer pourquoi les jokers sont OK ...)

au fait

J'utilise le type générique de Java comme "générique" dans les phrases et la prononciation comme "générique". N'est-il pas difficile de dire «génériques»?

Recommended Posts

Comment gérer les éléments dont vous ne savez pas s'ils sont uniques ou en matrice dans GSON
Comment remplacer des caractères que vous ne comprenez pas [Principe]
Comment empêcher les utilisateurs qui ne sont pas connectés de passer à la nouvelle page ou à la page de modification
[Ruby] Comment compter les nombres pairs ou impairs dans un tableau
Comment déterminer si vous avez effleuré vers la gauche ou la droite dans PagerView
Que faire lorsque les modifications du servlet ne sont pas reflétées
Comment faire une conversion de base en Java
Que faire si les modifications ne sont pas reflétées dans le fichier manifeste JAR
10 barrages de dessin avec ● ou ■ qui sont susceptibles d'apparaître dans la formation (Java)