c3c616c4eb42923c980058e6a1289b9daa7177c1
[unical.git] / gson / com / google / gson / internal / bind / MapTypeAdapterFactory.java
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.class, new 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 private final boolean complexMapKeySerialization;
108
109 public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
110 boolean complexMapKeySerialization) {
111 this.constructorConstructor = constructorConstructor;
112 this.complexMapKeySerialization = complexMapKeySerialization;
113 }
114
115 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 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 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; i < keys.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; i < keys.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 }
This page took 0.028196 seconds and 3 git commands to generate.