• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 Square, 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  *    https://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 package com.squareup.moshi;
17 
18 import static com.squareup.moshi.internal.Util.generatedAdapter;
19 
20 import com.squareup.moshi.internal.Util;
21 import java.io.IOException;
22 import java.lang.annotation.Annotation;
23 import java.lang.reflect.Type;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import javax.annotation.Nullable;
30 
31 final class StandardJsonAdapters {
StandardJsonAdapters()32   private StandardJsonAdapters() {}
33 
34   public static final JsonAdapter.Factory FACTORY =
35       new JsonAdapter.Factory() {
36         @Override
37         public JsonAdapter<?> create(
38             Type type, Set<? extends Annotation> annotations, Moshi moshi) {
39           if (!annotations.isEmpty()) return null;
40           if (type == boolean.class) return BOOLEAN_JSON_ADAPTER;
41           if (type == byte.class) return BYTE_JSON_ADAPTER;
42           if (type == char.class) return CHARACTER_JSON_ADAPTER;
43           if (type == double.class) return DOUBLE_JSON_ADAPTER;
44           if (type == float.class) return FLOAT_JSON_ADAPTER;
45           if (type == int.class) return INTEGER_JSON_ADAPTER;
46           if (type == long.class) return LONG_JSON_ADAPTER;
47           if (type == short.class) return SHORT_JSON_ADAPTER;
48           if (type == Boolean.class) return BOOLEAN_JSON_ADAPTER.nullSafe();
49           if (type == Byte.class) return BYTE_JSON_ADAPTER.nullSafe();
50           if (type == Character.class) return CHARACTER_JSON_ADAPTER.nullSafe();
51           if (type == Double.class) return DOUBLE_JSON_ADAPTER.nullSafe();
52           if (type == Float.class) return FLOAT_JSON_ADAPTER.nullSafe();
53           if (type == Integer.class) return INTEGER_JSON_ADAPTER.nullSafe();
54           if (type == Long.class) return LONG_JSON_ADAPTER.nullSafe();
55           if (type == Short.class) return SHORT_JSON_ADAPTER.nullSafe();
56           if (type == String.class) return STRING_JSON_ADAPTER.nullSafe();
57           if (type == Object.class) return new ObjectJsonAdapter(moshi).nullSafe();
58 
59           Class<?> rawType = Types.getRawType(type);
60 
61           @Nullable JsonAdapter<?> generatedAdapter = generatedAdapter(moshi, type, rawType);
62           if (generatedAdapter != null) {
63             return generatedAdapter;
64           }
65 
66           if (rawType.isEnum()) {
67             //noinspection unchecked
68             return new EnumJsonAdapter<>((Class<? extends Enum>) rawType).nullSafe();
69           }
70           return null;
71         }
72       };
73 
74   private static final String ERROR_FORMAT = "Expected %s but was %s at path %s";
75 
rangeCheckNextInt(JsonReader reader, String typeMessage, int min, int max)76   static int rangeCheckNextInt(JsonReader reader, String typeMessage, int min, int max)
77       throws IOException {
78     int value = reader.nextInt();
79     if (value < min || value > max) {
80       throw new JsonDataException(
81           String.format(ERROR_FORMAT, typeMessage, value, reader.getPath()));
82     }
83     return value;
84   }
85 
86   static final JsonAdapter<Boolean> BOOLEAN_JSON_ADAPTER =
87       new JsonAdapter<Boolean>() {
88         @Override
89         public Boolean fromJson(JsonReader reader) throws IOException {
90           return reader.nextBoolean();
91         }
92 
93         @Override
94         public void toJson(JsonWriter writer, Boolean value) throws IOException {
95           writer.value(value.booleanValue());
96         }
97 
98         @Override
99         public String toString() {
100           return "JsonAdapter(Boolean)";
101         }
102       };
103 
104   static final JsonAdapter<Byte> BYTE_JSON_ADAPTER =
105       new JsonAdapter<Byte>() {
106         @Override
107         public Byte fromJson(JsonReader reader) throws IOException {
108           return (byte) rangeCheckNextInt(reader, "a byte", Byte.MIN_VALUE, 0xff);
109         }
110 
111         @Override
112         public void toJson(JsonWriter writer, Byte value) throws IOException {
113           writer.value(value.intValue() & 0xff);
114         }
115 
116         @Override
117         public String toString() {
118           return "JsonAdapter(Byte)";
119         }
120       };
121 
122   static final JsonAdapter<Character> CHARACTER_JSON_ADAPTER =
123       new JsonAdapter<Character>() {
124         @Override
125         public Character fromJson(JsonReader reader) throws IOException {
126           String value = reader.nextString();
127           if (value.length() > 1) {
128             throw new JsonDataException(
129                 String.format(ERROR_FORMAT, "a char", '"' + value + '"', reader.getPath()));
130           }
131           return value.charAt(0);
132         }
133 
134         @Override
135         public void toJson(JsonWriter writer, Character value) throws IOException {
136           writer.value(value.toString());
137         }
138 
139         @Override
140         public String toString() {
141           return "JsonAdapter(Character)";
142         }
143       };
144 
145   static final JsonAdapter<Double> DOUBLE_JSON_ADAPTER =
146       new JsonAdapter<Double>() {
147         @Override
148         public Double fromJson(JsonReader reader) throws IOException {
149           return reader.nextDouble();
150         }
151 
152         @Override
153         public void toJson(JsonWriter writer, Double value) throws IOException {
154           writer.value(value.doubleValue());
155         }
156 
157         @Override
158         public String toString() {
159           return "JsonAdapter(Double)";
160         }
161       };
162 
163   static final JsonAdapter<Float> FLOAT_JSON_ADAPTER =
164       new JsonAdapter<Float>() {
165         @Override
166         public Float fromJson(JsonReader reader) throws IOException {
167           float value = (float) reader.nextDouble();
168           // Double check for infinity after float conversion; many doubles > Float.MAX
169           if (!reader.isLenient() && Float.isInfinite(value)) {
170             throw new JsonDataException(
171                 "JSON forbids NaN and infinities: " + value + " at path " + reader.getPath());
172           }
173           return value;
174         }
175 
176         @Override
177         public void toJson(JsonWriter writer, Float value) throws IOException {
178           // Manual null pointer check for the float.class adapter.
179           if (value == null) {
180             throw new NullPointerException();
181           }
182           // Use the Number overload so we write out float precision instead of double precision.
183           writer.value(value);
184         }
185 
186         @Override
187         public String toString() {
188           return "JsonAdapter(Float)";
189         }
190       };
191 
192   static final JsonAdapter<Integer> INTEGER_JSON_ADAPTER =
193       new JsonAdapter<Integer>() {
194         @Override
195         public Integer fromJson(JsonReader reader) throws IOException {
196           return reader.nextInt();
197         }
198 
199         @Override
200         public void toJson(JsonWriter writer, Integer value) throws IOException {
201           writer.value(value.intValue());
202         }
203 
204         @Override
205         public String toString() {
206           return "JsonAdapter(Integer)";
207         }
208       };
209 
210   static final JsonAdapter<Long> LONG_JSON_ADAPTER =
211       new JsonAdapter<Long>() {
212         @Override
213         public Long fromJson(JsonReader reader) throws IOException {
214           return reader.nextLong();
215         }
216 
217         @Override
218         public void toJson(JsonWriter writer, Long value) throws IOException {
219           writer.value(value.longValue());
220         }
221 
222         @Override
223         public String toString() {
224           return "JsonAdapter(Long)";
225         }
226       };
227 
228   static final JsonAdapter<Short> SHORT_JSON_ADAPTER =
229       new JsonAdapter<Short>() {
230         @Override
231         public Short fromJson(JsonReader reader) throws IOException {
232           return (short) rangeCheckNextInt(reader, "a short", Short.MIN_VALUE, Short.MAX_VALUE);
233         }
234 
235         @Override
236         public void toJson(JsonWriter writer, Short value) throws IOException {
237           writer.value(value.intValue());
238         }
239 
240         @Override
241         public String toString() {
242           return "JsonAdapter(Short)";
243         }
244       };
245 
246   static final JsonAdapter<String> STRING_JSON_ADAPTER =
247       new JsonAdapter<String>() {
248         @Override
249         public String fromJson(JsonReader reader) throws IOException {
250           return reader.nextString();
251         }
252 
253         @Override
254         public void toJson(JsonWriter writer, String value) throws IOException {
255           writer.value(value);
256         }
257 
258         @Override
259         public String toString() {
260           return "JsonAdapter(String)";
261         }
262       };
263 
264   static final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
265     private final Class<T> enumType;
266     private final String[] nameStrings;
267     private final T[] constants;
268     private final JsonReader.Options options;
269 
EnumJsonAdapter(Class<T> enumType)270     EnumJsonAdapter(Class<T> enumType) {
271       this.enumType = enumType;
272       try {
273         constants = enumType.getEnumConstants();
274         nameStrings = new String[constants.length];
275         for (int i = 0; i < constants.length; i++) {
276           T constant = constants[i];
277           String constantName = constant.name();
278           nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName));
279         }
280         options = JsonReader.Options.of(nameStrings);
281       } catch (NoSuchFieldException e) {
282         throw new AssertionError("Missing field in " + enumType.getName(), e);
283       }
284     }
285 
286     @Override
fromJson(JsonReader reader)287     public T fromJson(JsonReader reader) throws IOException {
288       int index = reader.selectString(options);
289       if (index != -1) return constants[index];
290 
291       // We can consume the string safely, we are terminating anyway.
292       String path = reader.getPath();
293       String name = reader.nextString();
294       throw new JsonDataException(
295           "Expected one of "
296               + Arrays.asList(nameStrings)
297               + " but was "
298               + name
299               + " at path "
300               + path);
301     }
302 
303     @Override
toJson(JsonWriter writer, T value)304     public void toJson(JsonWriter writer, T value) throws IOException {
305       writer.value(nameStrings[value.ordinal()]);
306     }
307 
308     @Override
toString()309     public String toString() {
310       return "JsonAdapter(" + enumType.getName() + ")";
311     }
312   }
313 
314   /**
315    * This adapter is used when the declared type is {@code java.lang.Object}. Typically the runtime
316    * type is something else, and when encoding JSON this delegates to the runtime type's adapter.
317    * For decoding (where there is no runtime type to inspect), this uses maps and lists.
318    *
319    * <p>This adapter needs a Moshi instance to look up the appropriate adapter for runtime types as
320    * they are encountered.
321    */
322   static final class ObjectJsonAdapter extends JsonAdapter<Object> {
323     private final Moshi moshi;
324     private final JsonAdapter<List> listJsonAdapter;
325     private final JsonAdapter<Map> mapAdapter;
326     private final JsonAdapter<String> stringAdapter;
327     private final JsonAdapter<Double> doubleAdapter;
328     private final JsonAdapter<Boolean> booleanAdapter;
329 
ObjectJsonAdapter(Moshi moshi)330     ObjectJsonAdapter(Moshi moshi) {
331       this.moshi = moshi;
332       this.listJsonAdapter = moshi.adapter(List.class);
333       this.mapAdapter = moshi.adapter(Map.class);
334       this.stringAdapter = moshi.adapter(String.class);
335       this.doubleAdapter = moshi.adapter(Double.class);
336       this.booleanAdapter = moshi.adapter(Boolean.class);
337     }
338 
339     @Override
fromJson(JsonReader reader)340     public Object fromJson(JsonReader reader) throws IOException {
341       switch (reader.peek()) {
342         case BEGIN_ARRAY:
343           return listJsonAdapter.fromJson(reader);
344 
345         case BEGIN_OBJECT:
346           return mapAdapter.fromJson(reader);
347 
348         case STRING:
349           return stringAdapter.fromJson(reader);
350 
351         case NUMBER:
352           return doubleAdapter.fromJson(reader);
353 
354         case BOOLEAN:
355           return booleanAdapter.fromJson(reader);
356 
357         case NULL:
358           return reader.nextNull();
359 
360         default:
361           throw new IllegalStateException(
362               "Expected a value but was " + reader.peek() + " at path " + reader.getPath());
363       }
364     }
365 
366     @Override
toJson(JsonWriter writer, Object value)367     public void toJson(JsonWriter writer, Object value) throws IOException {
368       Class<?> valueClass = value.getClass();
369       if (valueClass == Object.class) {
370         // Don't recurse infinitely when the runtime type is also Object.class.
371         writer.beginObject();
372         writer.endObject();
373       } else {
374         moshi.adapter(toJsonType(valueClass), Util.NO_ANNOTATIONS).toJson(writer, value);
375       }
376     }
377 
378     /**
379      * Returns the type to look up a type adapter for when writing {@code value} to JSON. Without
380      * this, attempts to emit standard types like `LinkedHashMap` would fail because Moshi doesn't
381      * provide built-in adapters for implementation types. It knows how to <strong>write</strong>
382      * those types, but lacks a mechanism to read them because it doesn't know how to find the
383      * appropriate constructor.
384      */
toJsonType(Class<?> valueClass)385     private Class<?> toJsonType(Class<?> valueClass) {
386       if (Map.class.isAssignableFrom(valueClass)) return Map.class;
387       if (Collection.class.isAssignableFrom(valueClass)) return Collection.class;
388       return valueClass;
389     }
390 
391     @Override
toString()392     public String toString() {
393       return "JsonAdapter(Object)";
394     }
395   }
396 }
397