1 /*
2  * Copyright (C) 2011 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package com.google.gson.internal.bind;
18
19 import com.google.gson.Gson;
20 import com.google.gson.JsonElement;
21 import com.google.gson.JsonPrimitive;
22 import com.google.gson.JsonSyntaxException;
23 import com.google.gson.TypeAdapter;
24 import com.google.gson.TypeAdapterFactory;
25 import com.google.gson.internal.$Gson$Types;
26 import com.google.gson.internal.ConstructorConstructor;
27 import com.google.gson.internal.JsonReaderInternalAccess;
28 import com.google.gson.internal.ObjectConstructor;
29 import com.google.gson.internal.Streams;
30 import com.google.gson.reflect.TypeToken;
31 import com.google.gson.stream.JsonReader;
32 import com.google.gson.stream.JsonToken;
33 import com.google.gson.stream.JsonWriter;
34 import java.io.IOException;
35 import java.lang.reflect.Type;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Map;
39
40 /**
41  * Adapts maps to either JSON objects or JSON arrays.
42  *
43  * <h3>Maps as JSON objects</h3>
44  * For primitive keys or when complex map key serialization is not enabled, this
45  * converts Java {@link Map Maps} to JSON Objects. This requires that map keys
46  * can be serialized as strings; this is insufficient for some key types. For
47  * example, consider a map whose keys are points on a grid. The default JSON
48  * form encodes reasonably: <pre>   {@code
49  *   Map<Point, String> original = new LinkedHashMap<Point, String>();
50  *   original.put(new Point(5, 6), "a");
51  *   original.put(new Point(8, 8), "b");
52  *   System.out.println(gson.toJson(original, type));
53  * }</pre>
54  * The above code prints this JSON object:<pre>   {@code
55  *   {
56  *     "(5,6)""a",
57  *     "(8,8)""b"
58  *   }
59  * }</pre>
60  * But GSON is unable to deserialize this value because the JSON string name is
61  * just the {@link Object#toString() toString()} of the map key. Attempting to
62  * convert the above JSON to an object fails with a parse exception:
63  * <pre>com.google.gson.JsonParseException: Expecting object found: "(5,6)"
64  *   at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
65  *   at com.google.gson.ObjectNavigator.navigateClassFields
66  *   ...</pre>
67  *
68  * <h3>Maps as JSON arrays</h3>
69  * An alternative approach taken by this type adapter when it is required and
70  * complex map key serialization is enabled is to encode maps as arrays of map
71  * entries. Each map entry is a two element array containing a key and a value.
72  * This approach is more flexible because any type can be used as the map's key;
73  * not just strings. But it's also less portable because the receiver of such
74  * JSON must be aware of the map entry convention.
75  *
76  * <p>Register this adapter when you are creating your GSON instance.
77  * <pre>   {@code
78  *   Gson gson = new GsonBuilder()
79  *     .registerTypeAdapter(Map.classnew MapAsArrayTypeAdapter())
80  *     .create();
81  * }</pre>
82  * This will change the structure of the JSON emitted by the code above. Now we
83  * get an array. In this case the arrays elements are map entries:
84  * <pre>   {@code
85  *   [
86  *     [
87  *       {
88  *         "x": 5,
89  *         "y": 6
90  *       },
91  *       "a",
92  *     ],
93  *     [
94  *       {
95  *         "x": 8,
96  *         "y": 8
97  *       },
98  *       "b"
99  *     ]
100  *   ]
101  * }</pre>
102  * This format will serialize and deserialize just fine as long as this adapter
103  * is registered.
104  */

105 public final class MapTypeAdapterFactory implements TypeAdapterFactory {
106   private final ConstructorConstructor constructorConstructor;
107   final boolean complexMapKeySerialization;
108
109   public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
110       boolean complexMapKeySerialization) {
111     this.constructorConstructor = constructorConstructor;
112     this.complexMapKeySerialization = complexMapKeySerialization;
113   }
114
115   @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
116     Type type = typeToken.getType();
117
118     Class<? super T> rawType = typeToken.getRawType();
119     if (!Map.class.isAssignableFrom(rawType)) {
120       return null;
121     }
122
123     Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type);
124     Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
125     TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyAndValueTypes[0]);
126     TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1]));
127     ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
128
129     @SuppressWarnings({"unchecked""rawtypes"})
130     // we don't define a type parameter for the key or value types
131     TypeAdapter<T> result = new Adapter(gson, keyAndValueTypes[0], keyAdapter,
132         keyAndValueTypes[1], valueAdapter, constructor);
133     return result;
134   }
135
136   /**
137    * Returns a type adapter that writes the value as a string.
138    */

139   private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
140     return (keyType == boolean.class || keyType == Boolean.class)
141         ? TypeAdapters.BOOLEAN_AS_STRING
142         : context.getAdapter(TypeToken.get(keyType));
143   }
144
145   private final class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
146     private final TypeAdapter<K> keyTypeAdapter;
147     private final TypeAdapter<V> valueTypeAdapter;
148     private final ObjectConstructor<? extends Map<K, V>> constructor;
149
150     public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter,
151         Type valueType, TypeAdapter<V> valueTypeAdapter,
152         ObjectConstructor<? extends Map<K, V>> constructor) {
153       this.keyTypeAdapter =
154         new TypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType);
155       this.valueTypeAdapter =
156         new TypeAdapterRuntimeTypeWrapper<V>(context, valueTypeAdapter, valueType);
157       this.constructor = constructor;
158     }
159
160     @Override public Map<K, V> read(JsonReader in) throws IOException {
161       JsonToken peek = in.peek();
162       if (peek == JsonToken.NULL) {
163         in.nextNull();
164         return null;
165       }
166
167       Map<K, V> map = constructor.construct();
168
169       if (peek == JsonToken.BEGIN_ARRAY) {
170         in.beginArray();
171         while (in.hasNext()) {
172           in.beginArray(); // entry array
173           K key = keyTypeAdapter.read(in);
174           V value = valueTypeAdapter.read(in);
175           V replaced = map.put(key, value);
176           if (replaced != null) {
177             throw new JsonSyntaxException("duplicate key: " + key);
178           }
179           in.endArray();
180         }
181         in.endArray();
182       } else {
183         in.beginObject();
184         while (in.hasNext()) {
185           JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
186           K key = keyTypeAdapter.read(in);
187           V value = valueTypeAdapter.read(in);
188           V replaced = map.put(key, value);
189           if (replaced != null) {
190             throw new JsonSyntaxException("duplicate key: " + key);
191           }
192         }
193         in.endObject();
194       }
195       return map;
196     }
197
198     @Override public void write(JsonWriter out, Map<K, V> map) throws IOException {
199       if (map == null) {
200         out.nullValue();
201         return;
202       }
203
204       if (!complexMapKeySerialization) {
205         out.beginObject();
206         for (Map.Entry<K, V> entry : map.entrySet()) {
207           out.name(String.valueOf(entry.getKey()));
208           valueTypeAdapter.write(out, entry.getValue());
209         }
210         out.endObject();
211         return;
212       }
213
214       boolean hasComplexKeys = false;
215       List<JsonElement> keys = new ArrayList<JsonElement>(map.size());
216
217       List<V> values = new ArrayList<V>(map.size());
218       for (Map.Entry<K, V> entry : map.entrySet()) {
219         JsonElement keyElement = keyTypeAdapter.toJsonTree(entry.getKey());
220         keys.add(keyElement);
221         values.add(entry.getValue());
222         hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject();
223       }
224
225       if (hasComplexKeys) {
226         out.beginArray();
227         for (int i = 0, size = keys.size(); i < size; i++) {
228           out.beginArray(); // entry array
229           Streams.write(keys.get(i), out);
230           valueTypeAdapter.write(out, values.get(i));
231           out.endArray();
232         }
233         out.endArray();
234       } else {
235         out.beginObject();
236         for (int i = 0, size = keys.size(); i < size; i++) {
237           JsonElement keyElement = keys.get(i);
238           out.name(keyToString(keyElement));
239           valueTypeAdapter.write(out, values.get(i));
240         }
241         out.endObject();
242       }
243     }
244
245     private String keyToString(JsonElement keyElement) {
246       if (keyElement.isJsonPrimitive()) {
247         JsonPrimitive primitive = keyElement.getAsJsonPrimitive();
248         if (primitive.isNumber()) {
249           return String.valueOf(primitive.getAsNumber());
250         } else if (primitive.isBoolean()) {
251           return Boolean.toString(primitive.getAsBoolean());
252         } else if (primitive.isString()) {
253           return primitive.getAsString();
254         } else {
255           throw new AssertionError();
256         }
257       } else if (keyElement.isJsonNull()) {
258         return "null";
259       } else {
260         throw new AssertionError();
261       }
262     }
263   }
264 }
265