1717
1818import com .google .gson .Gson ;
1919import com .google .gson .GsonBuilder ;
20+ import com .google .gson .JsonArray ;
2021import com .google .gson .JsonDeserializationContext ;
2122import com .google .gson .JsonDeserializer ;
2223import com .google .gson .JsonElement ;
2627import com .google .gson .JsonSerializer ;
2728
2829import java .lang .reflect .Type ;
30+ import java .util .ArrayList ;
31+ import java .util .List ;
2932
3033/**
3134 * Gson adapters to serialize/deserialize to/from data modeling types.
@@ -159,7 +162,9 @@ public QueryPredicate deserialize(JsonElement json, Type type, JsonDeserializati
159162 case OPERATION :
160163 return gson .fromJson (json , QueryPredicateOperation .class );
161164 case GROUP :
162- return gson .fromJson (json , QueryPredicateGroup .class );
165+ // We need to manually deserialize Groups to ensure we handle nested groups
166+ // and update _types correctly.
167+ return deserializeQueryPredicateGroup (jsonObject );
163168 case ALL :
164169 return gson .fromJson (json , MatchAllQueryPredicate .class );
165170 case NONE :
@@ -176,22 +181,98 @@ public QueryPredicate deserialize(JsonElement json, Type type, JsonDeserializati
176181 @ Override
177182 public JsonElement serialize (QueryPredicate predicate , Type type , JsonSerializationContext context )
178183 throws JsonParseException {
179- JsonElement json = gson . toJsonTree ( predicate ) ;
184+ JsonElement json ;
180185 PredicateType predicateType ;
181- if (predicate instanceof MatchAllQueryPredicate ) {
182- predicateType = PredicateType .ALL ;
183- } else if (predicate instanceof MatchNoneQueryPredicate ) {
184- predicateType = PredicateType .NONE ;
185- } else if (predicate instanceof QueryPredicateOperation ) {
186- predicateType = PredicateType .OPERATION ;
187- } else if (predicate instanceof QueryPredicateGroup ) {
186+ if (predicate instanceof QueryPredicateGroup ) {
187+ // We need to manually serialize Groups to ensure we handle nested groups and
188+ // update _types correctly.
188189 predicateType = PredicateType .GROUP ;
190+ json = serializeQueryPredicateGroup ((QueryPredicateGroup ) predicate , context );
189191 } else {
190- throw new JsonParseException ("Unable to identify the predicate type." );
192+ json = gson .toJsonTree (predicate );
193+ if (predicate instanceof MatchAllQueryPredicate ) {
194+ predicateType = PredicateType .ALL ;
195+ } else if (predicate instanceof MatchNoneQueryPredicate ) {
196+ predicateType = PredicateType .NONE ;
197+ } else if (predicate instanceof QueryPredicateOperation ) {
198+ predicateType = PredicateType .OPERATION ;
199+ } else {
200+ throw new JsonParseException ("Unable to identify the predicate type." );
201+ }
191202 }
192203 JsonObject jsonObject = json .getAsJsonObject ();
193204 jsonObject .addProperty (TYPE , predicateType .name ());
194205 return jsonObject ;
195206 }
207+
208+ /**
209+ * Serializes a QueryPredicateGroup to JSON format.
210+ * <p>
211+ * This method is necessary because QueryPredicateGroup contains nested QueryPredicate objects
212+ * that need to be recursively serialized. We cannot use context.serialize() directly on
213+ * QueryPredicateGroup because:
214+ * 1. Using context.serialize() would cause infinite recursion back to this adapter
215+ * 2. We need to manually construct the JSON structure with proper "_type" fields
216+ * <p>
217+ * The method handles:
218+ * - Serializing the group type (AND, OR, NOT)
219+ * - Recursively serializing each nested predicate in the predicates array
220+ * - Maintaining the correct JSON structure expected by the deserializer
221+ *
222+ * @param group The QueryPredicateGroup to serialize
223+ * @param context The serialization context for handling nested QueryOperator objects
224+ * @return JsonElement representing the serialized group
225+ */
226+ private JsonElement serializeQueryPredicateGroup (QueryPredicateGroup group , JsonSerializationContext context ) {
227+ JsonObject jsonObject = new JsonObject ();
228+ jsonObject .addProperty ("type" , group .type ().name ());
229+
230+ JsonArray predicatesArray = new JsonArray ();
231+ for (QueryPredicate predicate : group .predicates ()) {
232+ // Recursively serialize nested predicates using this adapter
233+ predicatesArray .add (serialize (predicate , QueryPredicate .class , context ));
234+ }
235+ jsonObject .add ("predicates" , predicatesArray );
236+
237+ return jsonObject ;
238+ }
239+
240+ /**
241+ * Deserializes a JSON object into a QueryPredicateGroup.
242+ * <p>
243+ * This method is necessary because QueryPredicateGroup contains nested QueryPredicate objects
244+ * that need to be recursively deserialized. We cannot use context.deserialize() directly
245+ * because:
246+ * 1. Using context.deserialize() would cause infinite recursion back to this adapter
247+ * 2. We need to manually parse the JSON structure and handle nested predicates
248+ * <p>
249+ * The method handles:
250+ * - Parsing the group type (AND, OR, NOT) from the "type" field
251+ * - Recursively deserializing each predicate in the "predicates" array
252+ * - Creating the QueryPredicateGroup with the correct constructor (no builder available)
253+ * <p>
254+ * This is critical for DataStore sync expressions that contain nested predicate groups,
255+ * which caused the "Interfaces can't be instantiated" error before this fix.
256+ *
257+ * @param jsonObject The JSON object containing the group data
258+ * @return QueryPredicateGroup instance with all nested predicates deserialized
259+ */
260+ private QueryPredicateGroup deserializeQueryPredicateGroup (JsonObject jsonObject ) {
261+ QueryPredicateGroup .Type type = QueryPredicateGroup .Type .valueOf (
262+ jsonObject .get ("type" ).getAsString ()
263+ );
264+
265+ List <QueryPredicate > predicates = new ArrayList <>();
266+ JsonArray predicatesArray = jsonObject .getAsJsonArray ("predicates" );
267+ for (JsonElement predicateElement : predicatesArray ) {
268+ // Recursively deserialize nested predicates using this adapter
269+ // Note: Passing null for context since we handle recursion manually
270+ QueryPredicate predicate = deserialize (predicateElement , QueryPredicate .class , null );
271+ predicates .add (predicate );
272+ }
273+
274+ // Use constructor since QueryPredicateGroup doesn't have a builder
275+ return new QueryPredicateGroup (type , predicates );
276+ }
196277 }
197278}
0 commit comments