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