1
16 package com.squareup.moshi;
17
18 import com.squareup.moshi.internal.Util;
19 import java.io.IOException;
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Type;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import javax.annotation.Nullable;
28
29 import static com.squareup.moshi.internal.Util.generatedAdapter;
30
31 final class StandardJsonAdapters {
32 private StandardJsonAdapters() {
33 }
34
35 public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
36 @Override public JsonAdapter<?> create(
37 Type type, Set<? extends Annotation> annotations, Moshi moshi) {
38 if (!annotations.isEmpty()) return null;
39 if (type == boolean.class) return BOOLEAN_JSON_ADAPTER;
40 if (type == byte.class) return BYTE_JSON_ADAPTER;
41 if (type == char.class) return CHARACTER_JSON_ADAPTER;
42 if (type == double.class) return DOUBLE_JSON_ADAPTER;
43 if (type == float.class) return FLOAT_JSON_ADAPTER;
44 if (type == int.class) return INTEGER_JSON_ADAPTER;
45 if (type == long.class) return LONG_JSON_ADAPTER;
46 if (type == short.class) return SHORT_JSON_ADAPTER;
47 if (type == Boolean.class) return BOOLEAN_JSON_ADAPTER.nullSafe();
48 if (type == Byte.class) return BYTE_JSON_ADAPTER.nullSafe();
49 if (type == Character.class) return CHARACTER_JSON_ADAPTER.nullSafe();
50 if (type == Double.class) return DOUBLE_JSON_ADAPTER.nullSafe();
51 if (type == Float.class) return FLOAT_JSON_ADAPTER.nullSafe();
52 if (type == Integer.class) return INTEGER_JSON_ADAPTER.nullSafe();
53 if (type == Long.class) return LONG_JSON_ADAPTER.nullSafe();
54 if (type == Short.class) return SHORT_JSON_ADAPTER.nullSafe();
55 if (type == String.class) return STRING_JSON_ADAPTER.nullSafe();
56 if (type == Object.class) return new ObjectJsonAdapter(moshi).nullSafe();
57
58 Class<?> rawType = Types.getRawType(type);
59
60 @Nullable JsonAdapter<?> generatedAdapter = generatedAdapter(moshi, type, rawType);
61 if (generatedAdapter != null) {
62 return generatedAdapter;
63 }
64
65 if (rawType.isEnum()) {
66
67 return new EnumJsonAdapter<>((Class<? extends Enum>) rawType).nullSafe();
68 }
69 return null;
70 }
71 };
72
73 private static final String ERROR_FORMAT = "Expected %s but was %s at path %s";
74
75 static int rangeCheckNextInt(JsonReader reader, String typeMessage, int min, int max)
76 throws IOException {
77 int value = reader.nextInt();
78 if (value < min || value > max) {
79 throw new JsonDataException(
80 String.format(ERROR_FORMAT, typeMessage, value, reader.getPath()));
81 }
82 return value;
83 }
84
85 static final JsonAdapter<Boolean> BOOLEAN_JSON_ADAPTER = new JsonAdapter<Boolean>() {
86 @Override public Boolean fromJson(JsonReader reader) throws IOException {
87 return reader.nextBoolean();
88 }
89
90 @Override public void toJson(JsonWriter writer, Boolean value) throws IOException {
91 writer.value(value.booleanValue());
92 }
93
94 @Override public String toString() {
95 return "JsonAdapter(Boolean)";
96 }
97 };
98
99 static final JsonAdapter<Byte> BYTE_JSON_ADAPTER = new JsonAdapter<Byte>() {
100 @Override public Byte fromJson(JsonReader reader) throws IOException {
101 return (byte) rangeCheckNextInt(reader, "a byte", Byte.MIN_VALUE, 0xff);
102 }
103
104 @Override public void toJson(JsonWriter writer, Byte value) throws IOException {
105 writer.value(value.intValue() & 0xff);
106 }
107
108 @Override public String toString() {
109 return "JsonAdapter(Byte)";
110 }
111 };
112
113 static final JsonAdapter<Character> CHARACTER_JSON_ADAPTER = new JsonAdapter<Character>() {
114 @Override public Character fromJson(JsonReader reader) throws IOException {
115 String value = reader.nextString();
116 if (value.length() > 1) {
117 throw new JsonDataException(
118 String.format(ERROR_FORMAT, "a char", '"' + value + '"', reader.getPath()));
119 }
120 return value.charAt(0);
121 }
122
123 @Override public void toJson(JsonWriter writer, Character value) throws IOException {
124 writer.value(value.toString());
125 }
126
127 @Override public String toString() {
128 return "JsonAdapter(Character)";
129 }
130 };
131
132 static final JsonAdapter<Double> DOUBLE_JSON_ADAPTER = new JsonAdapter<Double>() {
133 @Override public Double fromJson(JsonReader reader) throws IOException {
134 return reader.nextDouble();
135 }
136
137 @Override public void toJson(JsonWriter writer, Double value) throws IOException {
138 writer.value(value.doubleValue());
139 }
140
141 @Override public String toString() {
142 return "JsonAdapter(Double)";
143 }
144 };
145
146 static final JsonAdapter<Float> FLOAT_JSON_ADAPTER = new JsonAdapter<Float>() {
147 @Override public Float fromJson(JsonReader reader) throws IOException {
148 float value = (float) reader.nextDouble();
149
150 if (!reader.isLenient() && Float.isInfinite(value)) {
151 throw new JsonDataException("JSON forbids NaN and infinities: " + value
152 + " at path " + reader.getPath());
153 }
154 return value;
155 }
156
157 @Override public void toJson(JsonWriter writer, Float value) throws IOException {
158
159 if (value == null) {
160 throw new NullPointerException();
161 }
162
163 writer.value(value);
164 }
165
166 @Override public String toString() {
167 return "JsonAdapter(Float)";
168 }
169 };
170
171 static final JsonAdapter<Integer> INTEGER_JSON_ADAPTER = new JsonAdapter<Integer>() {
172 @Override public Integer fromJson(JsonReader reader) throws IOException {
173 return reader.nextInt();
174 }
175
176 @Override public void toJson(JsonWriter writer, Integer value) throws IOException {
177 writer.value(value.intValue());
178 }
179
180 @Override public String toString() {
181 return "JsonAdapter(Integer)";
182 }
183 };
184
185 static final JsonAdapter<Long> LONG_JSON_ADAPTER = new JsonAdapter<Long>() {
186 @Override public Long fromJson(JsonReader reader) throws IOException {
187 return reader.nextLong();
188 }
189
190 @Override public void toJson(JsonWriter writer, Long value) throws IOException {
191 writer.value(value.longValue());
192 }
193
194 @Override public String toString() {
195 return "JsonAdapter(Long)";
196 }
197 };
198
199 static final JsonAdapter<Short> SHORT_JSON_ADAPTER = new JsonAdapter<Short>() {
200 @Override public Short fromJson(JsonReader reader) throws IOException {
201 return (short) rangeCheckNextInt(reader, "a short", Short.MIN_VALUE, Short.MAX_VALUE);
202 }
203
204 @Override public void toJson(JsonWriter writer, Short value) throws IOException {
205 writer.value(value.intValue());
206 }
207
208 @Override public String toString() {
209 return "JsonAdapter(Short)";
210 }
211 };
212
213 static final JsonAdapter<String> STRING_JSON_ADAPTER = new JsonAdapter<String>() {
214 @Override public String fromJson(JsonReader reader) throws IOException {
215 return reader.nextString();
216 }
217
218 @Override public void toJson(JsonWriter writer, String value) throws IOException {
219 writer.value(value);
220 }
221
222 @Override public String toString() {
223 return "JsonAdapter(String)";
224 }
225 };
226
227 static final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
228 private final Class<T> enumType;
229 private final String[] nameStrings;
230 private final T[] constants;
231 private final JsonReader.Options options;
232
233 EnumJsonAdapter(Class<T> enumType) {
234 this.enumType = enumType;
235 try {
236 constants = enumType.getEnumConstants();
237 nameStrings = new String[constants.length];
238 for (int i = 0; i < constants.length; i++) {
239 T constant = constants[i];
240 Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class);
241 String name = annotation != null ? annotation.name() : constant.name();
242 nameStrings[i] = name;
243 }
244 options = JsonReader.Options.of(nameStrings);
245 } catch (NoSuchFieldException e) {
246 throw new AssertionError("Missing field in " + enumType.getName(), e);
247 }
248 }
249
250 @Override public T fromJson(JsonReader reader) throws IOException {
251 int index = reader.selectString(options);
252 if (index != -1) return constants[index];
253
254
255 String path = reader.getPath();
256 String name = reader.nextString();
257 throw new JsonDataException("Expected one of "
258 + Arrays.asList(nameStrings) + " but was " + name + " at path " + path);
259 }
260
261 @Override public void toJson(JsonWriter writer, T value) throws IOException {
262 writer.value(nameStrings[value.ordinal()]);
263 }
264
265 @Override public String toString() {
266 return "JsonAdapter(" + enumType.getName() + ")";
267 }
268 }
269
270
278 static final class ObjectJsonAdapter extends JsonAdapter<Object> {
279 private final Moshi moshi;
280 private final JsonAdapter<List> listJsonAdapter;
281 private final JsonAdapter<Map> mapAdapter;
282 private final JsonAdapter<String> stringAdapter;
283 private final JsonAdapter<Double> doubleAdapter;
284 private final JsonAdapter<Boolean> booleanAdapter;
285
286 ObjectJsonAdapter(Moshi moshi) {
287 this.moshi = moshi;
288 this.listJsonAdapter = moshi.adapter(List.class);
289 this.mapAdapter = moshi.adapter(Map.class);
290 this.stringAdapter = moshi.adapter(String.class);
291 this.doubleAdapter = moshi.adapter(Double.class);
292 this.booleanAdapter = moshi.adapter(Boolean.class);
293 }
294
295 @Override public Object fromJson(JsonReader reader) throws IOException {
296 switch (reader.peek()) {
297 case BEGIN_ARRAY:
298 return listJsonAdapter.fromJson(reader);
299
300 case BEGIN_OBJECT:
301 return mapAdapter.fromJson(reader);
302
303 case STRING:
304 return stringAdapter.fromJson(reader);
305
306 case NUMBER:
307 return doubleAdapter.fromJson(reader);
308
309 case BOOLEAN:
310 return booleanAdapter.fromJson(reader);
311
312 case NULL:
313 return reader.nextNull();
314
315 default:
316 throw new IllegalStateException(
317 "Expected a value but was " + reader.peek() + " at path " + reader.getPath());
318 }
319 }
320
321 @Override public void toJson(JsonWriter writer, Object value) throws IOException {
322 Class<?> valueClass = value.getClass();
323 if (valueClass == Object.class) {
324
325 writer.beginObject();
326 writer.endObject();
327 } else {
328 moshi.adapter(toJsonType(valueClass), Util.NO_ANNOTATIONS).toJson(writer, value);
329 }
330 }
331
332
339 private Class<?> toJsonType(Class<?> valueClass) {
340 if (Map.class.isAssignableFrom(valueClass)) return Map.class;
341 if (Collection.class.isAssignableFrom(valueClass)) return Collection.class;
342 return valueClass;
343 }
344
345 @Override public String toString() {
346 return "JsonAdapter(Object)";
347 }
348 }
349 }
350