[JAVA] How to handle items in GSON that do not know whether they are single or array

History

When I got the JSON with Redmine REST API, the following notation appeared in "custom_fields".

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

It seems that if the content of " value " is " multiple ": true, it is an array object, otherwise it is single. As in the above example, there are 3 patterns.

  1. Single item (multiple not specified)
  2. Array has one element
  3. Array has 2 or more elements

There is also a method to convert all to an array even if it is single at the time of deserialization (JSON → Java), but since it can not be restored to the original expression at the time of serialization (I do not know whether it is a single or an array with 1 element), put that control With that in mind, I also thought about a general-purpose implementation.

Implementation

Data object

The object to put data at the time of deserialization is as follows.

public class MultipleType<T> {

	private T value;

	private List<T> values;

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

	//Getter below/setter

}

Use either value or values and make the other null.

The above "custom_fields" looks like the following.

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

	//Getter below/setter
}

I want to use the functions of GSON as much as possible, so I will use the same field name as JSON.

Serializer / deserializer

Implement GSON's custom serializer / deserializer as follows.

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

}

There are three main points.

  1. When deserializing, use ʻisJsonArray ()` to check if it is an array and switch the process.
  2. Get the type parameter of MultipleType
  3. At the time of serialization, use ʻisMultiple ()` to check if it is an array and switch the process.

Especially 2 is a little complicated. First, we need to know what the argument Type typeOfT of thedeserializemethod represents. It contains an object that represents the type parameters of the JsonDeserializer interface. (To be exact, the type of object actually used at runtime) Generics type parameters are lost at compile time, so you can refer to them at run time.

The definition tells us that typeOfT isMultipleType <?>, But since we need to know the<?>Part, we are getting more internal type parameters with the getGenericType method. I will.

The reason why the runtime type of the type parameter <?> Is necessary is that if you give the correct type with the context.deserialize method, the deserialization after that can be left to the function of GSON. .. In particular, you don't have to handwrite complex structure deserialization where the fields of the data object are of non-primitive type (including String).

Where GSON is used

The usage of GSON is as follows.

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

Since the MultipleTypeAdapter class is not a generic type, it is a miso that there is no warning. This is why we used <?> (Wildcards) for the custom deserializer / serializer type parameters. (I can't explain why wildcards are OK ...)

by the way

I use the generic type of Java as "generics" in sentences and the pronunciation as "generics". Isn't it difficult to say "generics"?

Recommended Posts

How to handle items in GSON that do not know whether they are single or array
How to replace characters that you do not understand [Principle]
How to prevent users who are not logged in from transitioning to the new or edit page
[Ruby] How to count even or odd numbers in an array
How to determine whether you flicked left or right in PagerView
What to do when the changes in the Servlet are not reflected
How to do base conversion in Java
What to do if the changes are not reflected in the jar manifest file
10 barrages of drawing with ● or ■ that are likely to appear in training (Java)