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  *      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 package com.squareup.moshi;
17
18 import java.io.IOException;
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Type;
21 import java.math.BigDecimal;
22 import java.util.Set;
23 import javax.annotation.CheckReturnValue;
24 import javax.annotation.Nullable;
25 import okio.Buffer;
26 import okio.BufferedSink;
27 import okio.BufferedSource;
28
29 /**
30  * Converts Java values to JSON, and JSON values to Java.
31  */

32 public abstract class JsonAdapter<T> {
33   @CheckReturnValue public abstract @Nullable T fromJson(JsonReader reader) throws IOException;
34
35   @CheckReturnValue public final @Nullable T fromJson(BufferedSource source) throws IOException {
36     return fromJson(JsonReader.of(source));
37   }
38
39   @CheckReturnValue public final @Nullable T fromJson(String string) throws IOException {
40     JsonReader reader = JsonReader.of(new Buffer().writeUtf8(string));
41     T result = fromJson(reader);
42     if (!isLenient() && reader.peek() != JsonReader.Token.END_DOCUMENT) {
43       throw new JsonDataException("JSON document was not fully consumed.");
44     }
45     return result;
46   }
47
48   public abstract void toJson(JsonWriter writer, @Nullable T value) throws IOException;
49
50   public final void toJson(BufferedSink sink, @Nullable T value) throws IOException {
51     JsonWriter writer = JsonWriter.of(sink);
52     toJson(writer, value);
53   }
54
55   @CheckReturnValue public final String toJson(@Nullable T value) {
56     Buffer buffer = new Buffer();
57     try {
58       toJson(buffer, value);
59     } catch (IOException e) {
60       throw new AssertionError(e); // No I/O writing to a Buffer.
61     }
62     return buffer.readUtf8();
63   }
64
65   /**
66    * Encodes {@code value} as a Java value object comprised of maps, lists, strings, numbers,
67    * booleans, and nulls.
68    *
69    * <p>Values encoded using {@code value(double)} or {@code value(long)} are modeled with the
70    * corresponding boxed type. Values encoded using {@code value(Number)} are modeled as a
71    * {@link Long} for boxed integer types ({@link Byte}, {@link Short}, {@link Integer}, and {@link
72    * Long}), as a {@link Double} for boxed floating point types ({@link Float} and {@link Double}),
73    * and as a {@link BigDecimal} for all other types.
74    */

75   @CheckReturnValue public final @Nullable Object toJsonValue(@Nullable T value) {
76     JsonValueWriter writer = new JsonValueWriter();
77     try {
78       toJson(writer, value);
79       return writer.root();
80     } catch (IOException e) {
81       throw new AssertionError(e); // No I/O writing to an object.
82     }
83   }
84
85   /**
86    * Decodes a Java value object from {@code value}, which must be comprised of maps, lists,
87    * strings, numbers, booleans and nulls.
88    */

89   @CheckReturnValue public final @Nullable T fromJsonValue(@Nullable Object value) {
90     JsonValueReader reader = new JsonValueReader(value);
91     try {
92       return fromJson(reader);
93     } catch (IOException e) {
94       throw new AssertionError(e); // No I/O reading from an object.
95     }
96   }
97
98   /**
99    * Returns a JSON adapter equal to this JSON adapter, but that serializes nulls when encoding
100    * JSON.
101    */

102   @CheckReturnValue public final JsonAdapter<T> serializeNulls() {
103     final JsonAdapter<T> delegate = this;
104     return new JsonAdapter<T>() {
105       @Override public @Nullable T fromJson(JsonReader reader) throws IOException {
106         return delegate.fromJson(reader);
107       }
108       @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
109         boolean serializeNulls = writer.getSerializeNulls();
110         writer.setSerializeNulls(true);
111         try {
112           delegate.toJson(writer, value);
113         } finally {
114           writer.setSerializeNulls(serializeNulls);
115         }
116       }
117       @Override boolean isLenient() {
118         return delegate.isLenient();
119       }
120       @Override public String toString() {
121         return delegate + ".serializeNulls()";
122       }
123     };
124   }
125
126   /**
127    * Returns a JSON adapter equal to this JSON adapter, but with support for reading and writing
128    * nulls.
129    */

130   @CheckReturnValue public final JsonAdapter<T> nullSafe() {
131     final JsonAdapter<T> delegate = this;
132     return new JsonAdapter<T>() {
133       @Override public @Nullable T fromJson(JsonReader reader) throws IOException {
134         if (reader.peek() == JsonReader.Token.NULL) {
135           return reader.nextNull();
136         } else {
137           return delegate.fromJson(reader);
138         }
139       }
140       @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
141         if (value == null) {
142           writer.nullValue();
143         } else {
144           delegate.toJson(writer, value);
145         }
146       }
147       @Override boolean isLenient() {
148         return delegate.isLenient();
149       }
150       @Override public String toString() {
151         return delegate + ".nullSafe()";
152       }
153     };
154   }
155
156   /**
157    * Returns a JSON adapter equal to this JSON adapter, but that refuses null values. If null is
158    * read or written this will throw a {@link JsonDataException}.
159    *
160    * <p>Note that this adapter will not usually be invoked for absent values and so those must be
161    * handled elsewhere. This should only be used to fail on explicit nulls.
162    */

163   @CheckReturnValue public final JsonAdapter<T> nonNull() {
164     final JsonAdapter<T> delegate = this;
165     return new JsonAdapter<T>() {
166       @Override public @Nullable T fromJson(JsonReader reader) throws IOException {
167         if (reader.peek() == JsonReader.Token.NULL) {
168           throw new JsonDataException("Unexpected null at " + reader.getPath());
169         } else {
170           return delegate.fromJson(reader);
171         }
172       }
173       @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
174         if (value == null) {
175           throw new JsonDataException("Unexpected null at " + writer.getPath());
176         } else {
177           delegate.toJson(writer, value);
178         }
179       }
180       @Override boolean isLenient() {
181         return delegate.isLenient();
182       }
183       @Override public String toString() {
184         return delegate + ".nonNull()";
185       }
186     };
187   }
188
189   /** Returns a JSON adapter equal to this, but is lenient when reading and writing. */
190   @CheckReturnValue public final JsonAdapter<T> lenient() {
191     final JsonAdapter<T> delegate = this;
192     return new JsonAdapter<T>() {
193       @Override public @Nullable T fromJson(JsonReader reader) throws IOException {
194         boolean lenient = reader.isLenient();
195         reader.setLenient(true);
196         try {
197           return delegate.fromJson(reader);
198         } finally {
199           reader.setLenient(lenient);
200         }
201       }
202       @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
203         boolean lenient = writer.isLenient();
204         writer.setLenient(true);
205         try {
206           delegate.toJson(writer, value);
207         } finally {
208           writer.setLenient(lenient);
209         }
210       }
211       @Override boolean isLenient() {
212         return true;
213       }
214       @Override public String toString() {
215         return delegate + ".lenient()";
216       }
217     };
218   }
219
220   /**
221    * Returns a JSON adapter equal to this, but that throws a {@link JsonDataException} when
222    * {@linkplain JsonReader#setFailOnUnknown(boolean) unknown names and values} are encountered.
223    * This constraint applies to both the top-level message handled by this type adapter as well as
224    * to nested messages.
225    */

226   @CheckReturnValue public final JsonAdapter<T> failOnUnknown() {
227     final JsonAdapter<T> delegate = this;
228     return new JsonAdapter<T>() {
229       @Override public @Nullable T fromJson(JsonReader reader) throws IOException {
230         boolean skipForbidden = reader.failOnUnknown();
231         reader.setFailOnUnknown(true);
232         try {
233           return delegate.fromJson(reader);
234         } finally {
235           reader.setFailOnUnknown(skipForbidden);
236         }
237       }
238       @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
239         delegate.toJson(writer, value);
240       }
241       @Override boolean isLenient() {
242         return delegate.isLenient();
243       }
244       @Override public String toString() {
245         return delegate + ".failOnUnknown()";
246       }
247     };
248   }
249
250   /**
251    * Return a JSON adapter equal to this, but using {@code indent} to control how the result is
252    * formatted. The {@code indent} string to be repeated for each level of indentation in the
253    * encoded document. If {@code indent.isEmpty()} the encoded document will be compact. Otherwise
254    * the encoded document will be more human-readable.
255    *
256    * @param indent a string containing only whitespace.
257    */

258   @CheckReturnValue public JsonAdapter<T> indent(final String indent) {
259     if (indent == null) {
260       throw new NullPointerException("indent == null");
261     }
262     final JsonAdapter<T> delegate = this;
263     return new JsonAdapter<T>() {
264       @Override public @Nullable T fromJson(JsonReader reader) throws IOException {
265         return delegate.fromJson(reader);
266       }
267       @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
268         String originalIndent = writer.getIndent();
269         writer.setIndent(indent);
270         try {
271           delegate.toJson(writer, value);
272         } finally {
273           writer.setIndent(originalIndent);
274         }
275       }
276       @Override boolean isLenient() {
277         return delegate.isLenient();
278       }
279       @Override public String toString() {
280         return delegate + ".indent(\"" + indent + "\")";
281       }
282     };
283   }
284
285   boolean isLenient() {
286     return false;
287   }
288
289   public interface Factory {
290     /**
291      * Attempts to create an adapter for {@code type} annotated with {@code annotations}. This
292      * returns the adapter if one was created, or null if this factory isn't capable of creating
293      * such an adapter.
294      *
295      * <p>Implementations may use {@link Moshi#adapter} to compose adapters of other types, or
296      * {@link Moshi#nextAdapter} to delegate to the underlying adapter of the same type.
297      */

298     @CheckReturnValue
299     @Nullable JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi);
300   }
301 }
302