2020package co .elastic .clients .json ;
2121
2222import co .elastic .clients .util .ObjectBuilder ;
23+ import jakarta .json .JsonArray ;
24+ import jakarta .json .JsonValue ;
2325import jakarta .json .stream .JsonLocation ;
2426import jakarta .json .stream .JsonParser ;
2527import jakarta .json .stream .JsonParser .Event ;
@@ -117,11 +119,76 @@ Union deserialize(JsonParser parser, JsonpMapper mapper, Event event, BiFunction
117119 }
118120 }
119121
122+ /**
123+ * An event handler for arrays that disambiguates multiple array members by buffering the array and inspecting
124+ * the JSON event of its first element.
125+ */
126+ private static class ArrayMemberHandler <Union , Kind , Member > extends EventHandler <Union , Kind , Member > {
127+ // Element JSON event -> array member accepting that element type
128+ private final Map <Event , SingleMemberHandler <Union , Kind , Member >> byElementEvent = new HashMap <>();
129+ // Fallback for empty arrays or unrecognized element types: first declared member wins
130+ private final SingleMemberHandler <Union , Kind , Member > defaultMember ;
131+
132+ ArrayMemberHandler (List <SingleMemberHandler <Union , Kind , Member >> members ) {
133+ this .defaultMember = members .get (0 );
134+ for (SingleMemberHandler <Union , Kind , Member > m : members ) {
135+ JsonpDeserializer <?> unwrapped = DelegatingDeserializer .unwrap (m .deserializer );
136+ JsonpDeserializer <?> item = ((JsonpDeserializerBase .ArrayDeserializer <?>) unwrapped ).itemDeserializer ();
137+ // Key on the element's native events (its canonical output), first writer wins on conflict
138+ for (Event e : item .nativeEvents ()) {
139+ byElementEvent .putIfAbsent (e , m );
140+ }
141+ }
142+ }
143+
144+ @ Override
145+ EnumSet <Event > nativeEvents () {
146+ return EnumSet .of (Event .START_ARRAY );
147+ }
148+
149+ @ Override
150+ Union deserialize (JsonParser parser , JsonpMapper mapper , Event event , BiFunction <Kind , Member , Union > buildFn ) {
151+ // event == START_ARRAY. Buffer the whole array so we can inspect it and then replay it.
152+ JsonArray array = parser .getArray ();
153+
154+ SingleMemberHandler <Union , Kind , Member > member = defaultMember ;
155+ // Find the first non-null element: leading nulls don't identify a variant
156+ for (JsonValue element : array ) {
157+ if (element .getValueType () != JsonValue .ValueType .NULL ) {
158+ SingleMemberHandler <Union , Kind , Member > found =
159+ byElementEvent .get (valueTypeToEvent (element .getValueType ()));
160+ if (found != null ) {
161+ member = found ;
162+ }
163+ break ;
164+ }
165+ }
166+
167+ // Replay the buffered array into the chosen member's array deserializer
168+ JsonParser arrayParser = JsonpUtils .jsonValueParser (array , mapper );
169+ return member .deserialize (arrayParser , mapper , arrayParser .next (), buildFn );
170+ }
171+ }
172+
173+ private static Event valueTypeToEvent (JsonValue .ValueType type ) {
174+ switch (type ) {
175+ case OBJECT : return Event .START_OBJECT ;
176+ case ARRAY : return Event .START_ARRAY ;
177+ case STRING : return Event .VALUE_STRING ;
178+ case NUMBER : return Event .VALUE_NUMBER ;
179+ case TRUE : return Event .VALUE_TRUE ;
180+ case FALSE : return Event .VALUE_FALSE ;
181+ case NULL : return Event .VALUE_NULL ;
182+ default : throw new IllegalArgumentException ("Unknown JSON value type: " + type );
183+ }
184+ }
185+
120186 public static class Builder <Union , Kind , Member > implements ObjectBuilder <JsonpDeserializer <Union >> {
121187
122188 private final BiFunction <Kind , Member , Union > buildFn ;
123189
124190 private final List <SingleMemberHandler <Union , Kind , Member >> objectMembers = new ArrayList <>();
191+ private final List <SingleMemberHandler <Union , Kind , Member >> arrayMembers = new ArrayList <>();
125192 private final Map <Event , EventHandler <Union , Kind , Member >> otherMembers = new HashMap <>();
126193 private final boolean allowAmbiguousPrimitive ;
127194
@@ -185,6 +252,10 @@ public Builder<Union, Kind, Member> addMember(Kind tag, JsonpDeserializer<? exte
185252 // also add it as a string
186253 addMember (Event .VALUE_STRING , tag , member );
187254 }
255+ } else if (unwrapped instanceof JsonpDeserializerBase .ArrayDeserializer ) {
256+ // All arrays produce START_ARRAY, so they can't be keyed directly in `otherMembers`.
257+ // They are disambiguated later by the JSON event of their first element (see build()).
258+ arrayMembers .add (new SingleMemberHandler <>(tag , deserializer ));
188259 } else {
189260 SingleMemberHandler <Union , Kind , Member > member = new SingleMemberHandler <>(tag , deserializer );
190261 for (Event e : deserializer .nativeEvents ()) {
@@ -204,15 +275,23 @@ public JsonpDeserializer<Union> build() {
204275 }
205276 }
206277
278+ if (arrayMembers .size () == 1 ) {
279+ // A single array member can be keyed directly on START_ARRAY, no disambiguation needed
280+ addMember (Event .START_ARRAY , arrayMembers .get (0 ).tag , arrayMembers .get (0 ));
281+ } else if (arrayMembers .size () > 1 ) {
282+ if (otherMembers .containsKey (Event .START_ARRAY )) {
283+ throw new AmbiguousUnionException (
284+ "Array member '" + arrayMembers .get (0 ).tag + "' conflicts with another START_ARRAY member" );
285+ }
286+ // Multiple array members are disambiguated by looking ahead at their first element
287+ otherMembers .put (Event .START_ARRAY , new ArrayMemberHandler <>(arrayMembers ));
288+ }
289+
207290 if (objectMembers .size () == 1 && !otherMembers .containsKey (Event .START_OBJECT )) {
208291 // A single deserializer handles objects: promote it to otherMembers as we don't need property-based disambiguation
209292 otherMembers .put (Event .START_OBJECT , objectMembers .remove (0 ));
210293 }
211294
212- // if (objectMembers.size() > 1) {
213- // System.out.println("multiple objects in " + buildFn);
214- // }
215-
216295 return new UnionDeserializer <>(objectMembers , otherMembers , buildFn );
217296 }
218297 }
0 commit comments