@@ -130,9 +130,8 @@ private static <M extends AccessibleObject & Member> void checkAccessible(Object
130130 }
131131
132132 private BoundField createBoundField (
133- final Gson context , final Field field , final String name ,
134- final TypeToken <?> fieldType , boolean serialize , boolean deserialize ,
135- final boolean blockInaccessible ) {
133+ final Gson context , final Field field , final String serializedName ,
134+ final TypeToken <?> fieldType , final boolean serialize , final boolean blockInaccessible ) {
136135
137136 final boolean isPrimitive = Primitives .isPrimitive (fieldType .getRawType ());
138137
@@ -159,10 +158,9 @@ private BoundField createBoundField(
159158 // Will never actually be used, but we set it to avoid confusing nullness-analysis tools
160159 writeTypeAdapter = typeAdapter ;
161160 }
162- return new BoundField (name , field , serialize , deserialize ) {
161+ return new BoundField (serializedName , field ) {
163162 @ Override void write (JsonWriter writer , Object source )
164163 throws IOException , IllegalAccessException {
165- if (!serialized ) return ;
166164 if (blockInaccessible ) {
167165 checkAccessible (source , field );
168166 }
@@ -173,7 +171,7 @@ private BoundField createBoundField(
173171 // avoid direct recursion
174172 return ;
175173 }
176- writer .name (name );
174+ writer .name (serializedName );
177175 writeTypeAdapter .write (writer , fieldValue );
178176 }
179177
@@ -196,13 +194,35 @@ void readIntoField(JsonReader reader, Object target)
196194 };
197195 }
198196
199- private Map <String , BoundField > getBoundFields (Gson context , TypeToken <?> type , Class <?> raw ,
200- boolean blockInaccessible ) {
201- Map <String , BoundField > result = new LinkedHashMap <>();
197+ private static class FieldsData {
198+ public static final FieldsData EMPTY = new FieldsData (Collections .<String , BoundField >emptyMap (), Collections .<BoundField >emptyList ());
199+
200+ /** Maps from JSON member name to field */
201+ public final Map <String , BoundField > deserializedFields ;
202+ public final List <BoundField > serializedFields ;
203+
204+ public FieldsData (Map <String , BoundField > deserializedFields , List <BoundField > serializedFields ) {
205+ this .deserializedFields = deserializedFields ;
206+ this .serializedFields = serializedFields ;
207+ }
208+ }
209+
210+ private static IllegalArgumentException createDuplicateFieldException (Class <?> declaringType , String duplicateName , Field field1 , Field field2 ) {
211+ throw new IllegalArgumentException ("Class " + declaringType .getName ()
212+ + " declares multiple JSON fields named '" + duplicateName + "'; conflict is caused"
213+ + " by fields " + ReflectionHelper .fieldToString (field1 ) + " and " + ReflectionHelper .fieldToString (field2 )
214+ + "\n See " + TroubleshootingGuide .createUrl ("duplicate-fields" ));
215+ }
216+
217+ private FieldsData getBoundFields (Gson context , TypeToken <?> type , Class <?> raw , boolean blockInaccessible ) {
202218 if (raw .isInterface ()) {
203- return result ;
219+ return FieldsData . EMPTY ;
204220 }
205221
222+ Map <String , BoundField > deserializedFields = new LinkedHashMap <>();
223+ // For serialized fields use a Map to track duplicate field names; otherwise this could be a List<BoundField> instead
224+ Map <String , BoundField > serializedFields = new LinkedHashMap <>();
225+
206226 Class <?> originalRaw = raw ;
207227 while (raw != Object .class ) {
208228 Field [] fields = raw .getDeclaredFields ();
@@ -229,44 +249,47 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,
229249 if (!blockInaccessible ) {
230250 ReflectionHelper .makeAccessible (field );
231251 }
252+
232253 Type fieldType = $Gson$Types .resolve (type .getType (), raw , field .getGenericType ());
233254 List <String > fieldNames = getFieldNames (field );
234- BoundField previous = null ;
235- for (int i = 0 , size = fieldNames .size (); i < size ; ++i ) {
236- String name = fieldNames .get (i );
237- if (i != 0 ) serialize = false ; // only serialize the default name
238- BoundField boundField = createBoundField (context , field , name ,
239- TypeToken .get (fieldType ), serialize , deserialize , blockInaccessible );
240- BoundField replaced = result .put (name , boundField );
241- if (previous == null ) previous = replaced ;
255+ String serializedName = fieldNames .get (0 );
256+ BoundField boundField = createBoundField (context , field , serializedName ,
257+ TypeToken .get (fieldType ), serialize , blockInaccessible );
258+
259+ if (deserialize ) {
260+ for (String name : fieldNames ) {
261+ BoundField replaced = deserializedFields .put (name , boundField );
262+
263+ if (replaced != null ) {
264+ throw createDuplicateFieldException (originalRaw , name , replaced .field , field );
265+ }
266+ }
242267 }
243- if (previous != null ) {
244- throw new IllegalArgumentException ("Class " + originalRaw .getName ()
245- + " declares multiple JSON fields named '" + previous .name + "'; conflict is caused"
246- + " by fields " + ReflectionHelper .fieldToString (previous .field ) + " and " + ReflectionHelper .fieldToString (field )
247- + "\n See " + TroubleshootingGuide .createUrl ("duplicate-fields" ));
268+
269+ if (serialize ) {
270+ BoundField replaced = serializedFields .put (serializedName , boundField );
271+ if (replaced != null ) {
272+ throw createDuplicateFieldException (originalRaw , serializedName , replaced .field , field );
273+ }
248274 }
249275 }
250276 type = TypeToken .get ($Gson$Types .resolve (type .getType (), raw , raw .getGenericSuperclass ()));
251277 raw = type .getRawType ();
252278 }
253- return result ;
279+ return new FieldsData ( deserializedFields , new ArrayList <>( serializedFields . values ())) ;
254280 }
255281
256282 static abstract class BoundField {
257- final String name ;
283+ /** Name used for serialization (but not for deserialization) */
284+ final String serializedName ;
258285 final Field field ;
259286 /** Name of the underlying field */
260287 final String fieldName ;
261- final boolean serialized ;
262- final boolean deserialized ;
263288
264- protected BoundField (String name , Field field , boolean serialized , boolean deserialized ) {
265- this .name = name ;
289+ protected BoundField (String serializedName , Field field ) {
290+ this .serializedName = serializedName ;
266291 this .field = field ;
267292 this .fieldName = field .getName ();
268- this .serialized = serialized ;
269- this .deserialized = deserialized ;
270293 }
271294
272295 /** Read this field value from the source, and append its JSON value to the writer */
@@ -288,10 +311,10 @@ protected BoundField(String name, Field field, boolean serialized, boolean deser
288311 */
289312 // This class is public because external projects check for this class with `instanceof` (even though it is internal)
290313 public static abstract class Adapter <T , A > extends TypeAdapter <T > {
291- final Map < String , BoundField > boundFields ;
314+ private final FieldsData fieldsData ;
292315
293- Adapter (Map < String , BoundField > boundFields ) {
294- this .boundFields = boundFields ;
316+ Adapter (FieldsData fieldsData ) {
317+ this .fieldsData = fieldsData ;
295318 }
296319
297320 @ Override
@@ -303,7 +326,7 @@ public void write(JsonWriter out, T value) throws IOException {
303326
304327 out .beginObject ();
305328 try {
306- for (BoundField boundField : boundFields . values () ) {
329+ for (BoundField boundField : fieldsData . serializedFields ) {
307330 boundField .write (out , value );
308331 }
309332 } catch (IllegalAccessException e ) {
@@ -320,13 +343,14 @@ public T read(JsonReader in) throws IOException {
320343 }
321344
322345 A accumulator = createAccumulator ();
346+ Map <String , BoundField > deserializedFields = fieldsData .deserializedFields ;
323347
324348 try {
325349 in .beginObject ();
326350 while (in .hasNext ()) {
327351 String name = in .nextName ();
328- BoundField field = boundFields .get (name );
329- if (field == null || ! field . deserialized ) {
352+ BoundField field = deserializedFields .get (name );
353+ if (field == null ) {
330354 in .skipValue ();
331355 } else {
332356 readField (accumulator , in , field );
@@ -356,8 +380,8 @@ abstract void readField(A accumulator, JsonReader in, BoundField field)
356380 private static final class FieldReflectionAdapter <T > extends Adapter <T , T > {
357381 private final ObjectConstructor <T > constructor ;
358382
359- FieldReflectionAdapter (ObjectConstructor <T > constructor , Map < String , BoundField > boundFields ) {
360- super (boundFields );
383+ FieldReflectionAdapter (ObjectConstructor <T > constructor , FieldsData fieldsData ) {
384+ super (fieldsData );
361385 this .constructor = constructor ;
362386 }
363387
0 commit comments