44import io .sentry .vendor .gson .stream .JsonToken ;
55import java .io .IOException ;
66import java .io .Reader ;
7+ import java .util .ArrayDeque ;
78import java .util .ArrayList ;
89import java .util .Date ;
10+ import java .util .Deque ;
911import java .util .HashMap ;
1012import java .util .List ;
1113import java .util .Map ;
1820public final class JsonObjectReader implements ObjectReader {
1921
2022 private final @ NotNull JsonReader jsonReader ;
23+ private final @ NotNull Deque <RecoveryState > recoveryStates = new ArrayDeque <>();
2124 private int depth = 0 ;
2225
2326 public JsonObjectReader (Reader in ) {
@@ -85,18 +88,21 @@ public float nextFloat() throws IOException {
8588
8689 @ Override
8790 public void nextUnknown (ILogger logger , Map <String , Object > unknown , String name ) {
88- final int startDepth = depth ;
89- JsonToken startToken = JsonToken .END_DOCUMENT ;
91+ RecoveryState recoveryState = null ;
9092 try {
91- startToken = peek ();
93+ recoveryState = beginRecovery ( peek () );
9294 unknown .put (name , nextObjectOrNull ());
9395 } catch (Exception exception ) {
9496 logger .log (SentryLevel .ERROR , exception , "Error deserializing unknown key: %s" , name );
95- try {
96- recoverValue (startDepth , startToken );
97- } catch (Exception ignored ) {
98- // stream is unrecoverable
97+ if (recoveryState != null ) {
98+ try {
99+ recoverValue (recoveryState );
100+ } catch (Exception ignored ) {
101+ // stream is unrecoverable
102+ }
99103 }
104+ } finally {
105+ endRecovery (recoveryState );
100106 }
101107 }
102108
@@ -111,8 +117,7 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
111117 List <T > list = new ArrayList <>();
112118 if (jsonReader .hasNext ()) {
113119 do {
114- final int startDepth = depth ;
115- final JsonToken startToken = peek ();
120+ final RecoveryState recoveryState = beginRecovery (peek ());
116121 try {
117122 list .add (deserializer .deserialize (this , logger ));
118123 } catch (Exception e ) {
@@ -121,10 +126,11 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
121126 e ,
122127 "Failed to deserialize object in list." ,
123128 "Stream unrecoverable, aborting list deserialization." ,
124- startDepth ,
125- startToken )) {
129+ recoveryState )) {
126130 break ;
127131 }
132+ } finally {
133+ endRecovery (recoveryState );
128134 }
129135 } while (jsonReader .peek () == JsonToken .BEGIN_OBJECT );
130136 }
@@ -144,8 +150,7 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
144150 if (jsonReader .hasNext ()) {
145151 do {
146152 final String key = jsonReader .nextName ();
147- final int startDepth = depth ;
148- final JsonToken startToken = peek ();
153+ final RecoveryState recoveryState = beginRecovery (peek ());
149154 try {
150155 map .put (key , deserializer .deserialize (this , logger ));
151156 } catch (Exception e ) {
@@ -154,10 +159,11 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
154159 e ,
155160 "Failed to deserialize object in map." ,
156161 "Stream unrecoverable, aborting map deserialization." ,
157- startDepth ,
158- startToken )) {
162+ recoveryState )) {
159163 break ;
160164 }
165+ } finally {
166+ endRecovery (recoveryState );
161167 }
162168 } while (jsonReader .peek () == JsonToken .BEGIN_OBJECT || jsonReader .peek () == JsonToken .NAME );
163169 }
@@ -180,8 +186,7 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
180186 if (hasNext ()) {
181187 do {
182188 final @ NotNull String key = nextName ();
183- final int startDepth = depth ;
184- final JsonToken startToken = peek ();
189+ final RecoveryState recoveryState = beginRecovery (peek ());
185190 try {
186191 final @ Nullable List <T > list = nextListOrNull (logger , deserializer );
187192 if (list != null ) {
@@ -193,10 +198,11 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
193198 e ,
194199 "Failed to deserialize list in map." ,
195200 "Stream unrecoverable, aborting map-of-lists deserialization." ,
196- startDepth ,
197- startToken )) {
201+ recoveryState )) {
198202 break ;
199203 }
204+ } finally {
205+ endRecovery (recoveryState );
200206 }
201207 } while (peek () == JsonToken .BEGIN_OBJECT || peek () == JsonToken .NAME );
202208 }
@@ -263,6 +269,7 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
263269 public void beginObject () throws IOException {
264270 jsonReader .beginObject ();
265271 depth ++;
272+ markContainerEntered ();
266273 }
267274
268275 @ Override
@@ -275,6 +282,7 @@ public void endObject() throws IOException {
275282 public void beginArray () throws IOException {
276283 jsonReader .beginArray ();
277284 depth ++;
285+ markContainerEntered ();
278286 }
279287
280288 @ Override
@@ -333,22 +341,46 @@ private boolean recoverAfterValueFailure(
333341 final @ NotNull Exception error ,
334342 final @ NotNull String warningMessage ,
335343 final @ NotNull String unrecoverableMessage ,
336- final int startDepth ,
337- final @ NotNull JsonToken startToken ) {
344+ final @ NotNull RecoveryState recoveryState ) {
338345 logger .log (SentryLevel .WARNING , warningMessage , error );
339346 try {
340- recoverValue (startDepth , startToken );
347+ recoverValue (recoveryState );
341348 return true ;
342349 } catch (Exception recoveryException ) {
343350 logger .log (SentryLevel .ERROR , unrecoverableMessage , recoveryException );
344351 return false ;
345352 }
346353 }
347354
348- private void recoverValue (final int startDepth , final @ NotNull JsonToken startToken )
349- throws IOException {
350- final boolean enteredNestedValue = depth > startDepth ;
351- while (depth > startDepth ) {
355+ private @ NotNull RecoveryState beginRecovery (final @ NotNull JsonToken startToken ) {
356+ final RecoveryState recoveryState = new RecoveryState (depth , startToken );
357+ recoveryStates .addLast (recoveryState );
358+ return recoveryState ;
359+ }
360+
361+ private void endRecovery (final @ Nullable RecoveryState recoveryState ) {
362+ if (recoveryState == null ) {
363+ return ;
364+ }
365+ if (!recoveryStates .isEmpty () && recoveryStates .peekLast () == recoveryState ) {
366+ recoveryStates .removeLast ();
367+ } else {
368+ recoveryStates .remove (recoveryState );
369+ }
370+ }
371+
372+ private void markContainerEntered () {
373+ for (RecoveryState recoveryState : recoveryStates ) {
374+ if (!recoveryState .enteredContainer
375+ && isContainerStartToken (recoveryState .startToken )
376+ && depth > recoveryState .startDepth ) {
377+ recoveryState .enteredContainer = true ;
378+ }
379+ }
380+ }
381+
382+ private void recoverValue (final @ NotNull RecoveryState recoveryState ) throws IOException {
383+ while (depth > recoveryState .startDepth ) {
352384 final JsonToken token = peek ();
353385 if (token == JsonToken .END_OBJECT ) {
354386 endObject ();
@@ -359,13 +391,28 @@ private void recoverValue(final int startDepth, final @NotNull JsonToken startTo
359391 }
360392 }
361393
362- if (!enteredNestedValue && peek () == startToken ) {
394+ if (!recoveryState . enteredContainer && peek () == recoveryState . startToken ) {
363395 skipValue ();
364396 }
365397 }
366398
399+ private static boolean isContainerStartToken (final @ NotNull JsonToken token ) {
400+ return token == JsonToken .BEGIN_OBJECT || token == JsonToken .BEGIN_ARRAY ;
401+ }
402+
367403 @ Override
368404 public void close () throws IOException {
369405 jsonReader .close ();
370406 }
407+
408+ private static final class RecoveryState {
409+ private final int startDepth ;
410+ private final @ NotNull JsonToken startToken ;
411+ private boolean enteredContainer ;
412+
413+ private RecoveryState (final int startDepth , final @ NotNull JsonToken startToken ) {
414+ this .startDepth = startDepth ;
415+ this .startToken = startToken ;
416+ }
417+ }
371418}
0 commit comments