/* * Copyright (C) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.gson.typeadapters; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.Map; public final class RuntimeTypeAdapter implements JsonSerializer, JsonDeserializer { private final Class baseType; private final String typeFieldName; private final Map> labelToSubtype = new LinkedHashMap>(); private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); public RuntimeTypeAdapter(Class baseType, String typeFieldName) { this.baseType = baseType; this.typeFieldName = typeFieldName; } public static RuntimeTypeAdapter create(Class c) { return new RuntimeTypeAdapter(c, "type"); } public static RuntimeTypeAdapter create(Class c, String typeFieldName) { return new RuntimeTypeAdapter(c, typeFieldName); } public RuntimeTypeAdapter registerSubtype(Class type, String label) { if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { throw new IllegalArgumentException("types and labels must be unique"); } labelToSubtype.put(label, type); subtypeToLabel.put(type, label); return this; } public RuntimeTypeAdapter registerSubtype(Class type) { return registerSubtype(type, type.getSimpleName()); } public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) { Class srcType = src.getClass(); String label = subtypeToLabel.get(srcType); if (label == null) { throw new IllegalArgumentException("cannot serialize " + srcType.getName() + "; did you forget to register a subtype?"); } JsonElement serialized = context.serialize(src, srcType); final JsonObject jsonObject = serialized.getAsJsonObject(); if (jsonObject.has(typeFieldName)) { throw new IllegalArgumentException("cannot serialize " + srcType.getName() + " because it already defines a field named " + typeFieldName); } JsonObject clone = new JsonObject(); clone.add(typeFieldName, new JsonPrimitive(label)); for (Map.Entry e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } return clone; } public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonElement labelJsonElement = json.getAsJsonObject().remove(typeFieldName); if (labelJsonElement == null) { throw new JsonParseException("cannot deserialize " + typeOfT + " because it does not define a field named " + typeFieldName); } String label = labelJsonElement.getAsString(); Class subtype = labelToSubtype.get(label); if (subtype == null) { throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + label + "; did you forget to register a subtype?"); } @SuppressWarnings("unchecked") // registration requires that subtype extends T T result = (T) context.deserialize(json, subtype); return result; } }