Skip to content

Commit

Permalink
Add support for shortcut properties of non-primitive type
Browse files Browse the repository at this point in the history
  • Loading branch information
swallez committed Aug 6, 2024
1 parent f28bcd5 commit f1c76cb
Show file tree
Hide file tree
Showing 12 changed files with 119 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ protected static void setupFuzzyQueryDeserializer(ObjectDeserializer<FuzzyQuery.
op.add(Builder::value, FieldValue._DESERIALIZER, "value");

op.setKey(Builder::field, JsonpDeserializer.stringDeserializer());
op.shortcutProperty("value");
op.shortcutProperty("value", false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ protected static void setupMatchQueryDeserializer(ObjectDeserializer<MatchQuery.
op.add(Builder::zeroTermsQuery, ZeroTermsQuery._DESERIALIZER, "zero_terms_query");

op.setKey(Builder::field, JsonpDeserializer.stringDeserializer());
op.shortcutProperty("query");
op.shortcutProperty("query", false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ protected static void setupTermQueryDeserializer(ObjectDeserializer<TermQuery.Bu
op.add(Builder::caseInsensitive, JsonpDeserializer.booleanDeserializer(), "case_insensitive");

op.setKey(Builder::field, JsonpDeserializer.stringDeserializer());
op.shortcutProperty("value");
op.shortcutProperty("value", false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ protected static void setupRankEvalQueryDeserializer(ObjectDeserializer<RankEval
op.add(Builder::query, Query._DESERIALIZER, "query");
op.add(Builder::size, JsonpDeserializer.integerDeserializer(), "size");

op.shortcutProperty("query");
op.shortcutProperty("query", false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ protected static void setupCompletionContextDeserializer(ObjectDeserializer<Comp
op.add(Builder::precision, GeoHashPrecision._DESERIALIZER, "precision");
op.add(Builder::prefix, JsonpDeserializer.booleanDeserializer(), "prefix");

op.shortcutProperty("context");
op.shortcutProperty("context", false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ protected static void setupSourceFilterDeserializer(ObjectDeserializer<SourceFil
op.add(Builder::includes, JsonpDeserializer.arrayDeserializer(JsonpDeserializer.stringDeserializer()),
"includes", "include");

op.shortcutProperty("includes");
op.shortcutProperty("includes", false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ protected static void setupDataframeAnalysisAnalyzedFieldsDeserializer(
op.add(Builder::excludes, JsonpDeserializer.arrayDeserializer(JsonpDeserializer.stringDeserializer()),
"excludes");

op.shortcutProperty("includes");
op.shortcutProperty("includes", false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ protected static void setupRoleTemplateScriptDeserializer(ObjectDeserializer<Rol
op.add(Builder::options, JsonpDeserializer.stringMapDeserializer(JsonpDeserializer.stringDeserializer()),
"options");

op.shortcutProperty("source");
op.shortcutProperty("source", false);

}

Expand Down
32 changes: 32 additions & 0 deletions java-client/src/main/java/co/elastic/clients/json/JsonpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,38 @@ public String toString() {
}
}

/**
* In union types, find the variant to be used by looking up property names in the JSON stream until we find one that
* uniquely identifies the variant.
*
* @param <Variant> the type of variant descriptors used by the caller.
* @param variants a map of variant descriptors, keyed by the property name that uniquely identifies the variant.
* @return a pair containing the variant descriptor (or {@code null} if not found), and a parser to be used to read the JSON object.
*/

public static <Variant> Map.Entry<Variant, JsonParser> findVariant(
Map<String, Variant> variants, JsonParser parser, JsonpMapper mapper
) {
if (parser instanceof LookAheadJsonParser) {
return ((LookAheadJsonParser) parser).findVariant(variants);
} else {
// Parse as an object to find matching field names
JsonObject object = parser.getObject();

Variant variant = null;
for (String field: object.keySet()) {
variant = variants.get(field);
if (variant != null) {
break;
}
}

// Traverse the object we have inspected
parser = JsonpUtils.objectParser(object, mapper);
return new AbstractMap.SimpleImmutableEntry<>(variant, parser);
}
}

/**
* Create a parser that traverses a JSON object
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ public EnumSet<Event> acceptedEvents() {

//---------------------------------------------------------------------------------------------
private static final EnumSet<Event> EventSetObject = EnumSet.of(Event.START_OBJECT, Event.KEY_NAME);
private static final EnumSet<Event> EventSetObjectAndString = EnumSet.of(Event.START_OBJECT, Event.VALUE_STRING, Event.KEY_NAME);

private EnumSet<Event> acceptedEvents = EventSetObject; // May be changed in `shortcutProperty()`
private final Supplier<ObjectType> constructor;
Expand All @@ -115,6 +114,7 @@ public EnumSet<Event> acceptedEvents() {
private String typeProperty;
private String defaultType;
private FieldDeserializer<ObjectType> shortcutProperty;
private boolean isPrimitiveShortcut;
private QuadConsumer<ObjectType, String, JsonParser, JsonpMapper> unknownFieldHandler;

public ObjectDeserializer(Supplier<ObjectType> constructor) {
Expand Down Expand Up @@ -167,11 +167,33 @@ public ObjectType deserialize(ObjectType value, JsonParser parser, JsonpMapper m
event = parser.next();
}

if (shortcutProperty != null && event != Event.START_OBJECT && event != Event.KEY_NAME) {
// This is the shortcut property (should be a value event, this will be checked by its deserializer)
shortcutProperty.deserialize(parser, mapper, shortcutProperty.name, value, event);
if (shortcutProperty != null) {
if (isPrimitiveShortcut) {
if (event != Event.START_OBJECT && event != Event.KEY_NAME) {
// This is the shortcut property (should be a value event, this will be checked by its deserializer)
shortcutProperty.deserialize(parser, mapper, shortcutProperty.name, value, event);
return value;
}
} else {
// Does the shortcut property exist? If yes, the shortcut is used
Map.Entry<Object, JsonParser> shortcut = JsonpUtils.findVariant(
Collections.singletonMap(shortcutProperty.name, Boolean.TRUE /* arbitrary non-null value */),
parser, mapper
);

// Parse the buffered events
parser = shortcut.getValue();
parser.next(); // consume START_OBJECT

// If shortcut property was not found, this is a shortcut. Otherwise, keep deserializing as usual
if (shortcut.getKey() == null) {
shortcutProperty.deserialize(parser, mapper, shortcutProperty.name, value, event);
return value;
}
}
}

} else if (typeProperty == null) {
if (typeProperty == null) {
if (event != Event.START_OBJECT && event != Event.KEY_NAME) {
// Report we're waiting for a start_object, since this is the most common beginning for object parser
JsonpUtils.expectEvent(parser, Event.START_OBJECT, event);
Expand Down Expand Up @@ -249,14 +271,18 @@ public void ignore(String name) {
}

public void shortcutProperty(String name) {
shortcutProperty(name, true);
}

public void shortcutProperty(String name, boolean isPrimitive) {
this.shortcutProperty = this.fieldDeserializers.get(name);
if (this.shortcutProperty == null) {
throw new NoSuchElementException("No deserializer was setup for '" + name + "'");
}

//acceptedEvents = EnumSet.copyOf(acceptedEvents);
//acceptedEvents.addAll(shortcutProperty.acceptedEvents());
acceptedEvents = EventSetObjectAndString;
acceptedEvents = EnumSet.copyOf(acceptedEvents);
acceptedEvents.addAll(shortcutProperty.acceptedEvents());
this.isPrimitiveShortcut = isPrimitive;
}

//----- Object types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package co.elastic.clients.json;

import co.elastic.clients.util.ObjectBuilder;
import jakarta.json.JsonObject;
import jakarta.json.stream.JsonLocation;
import jakarta.json.stream.JsonParser;
import jakarta.json.stream.JsonParser.Event;
Expand Down Expand Up @@ -265,28 +264,12 @@ public Union deserialize(JsonParser parser, JsonpMapper mapper, Event event) {
JsonLocation location = parser.getLocation();

if (member == null && event == Event.START_OBJECT && !objectMembers.isEmpty()) {
if (parser instanceof LookAheadJsonParser) {
Map.Entry<EventHandler<Union, Kind, Member>, JsonParser> memberAndParser =
((LookAheadJsonParser) parser).findVariant(objectMembers);
Map.Entry<EventHandler<Union, Kind, Member>, JsonParser> memberAndParser =
JsonpUtils.findVariant(objectMembers, parser, mapper);

member = memberAndParser.getKey();
// Parse the buffered parser
parser = memberAndParser.getValue();

} else {
// Parse as an object to find matching field names
JsonObject object = parser.getObject();

for (String field: object.keySet()) {
member = objectMembers.get(field);
if (member != null) {
break;
}
}

// Traverse the object we have inspected
parser = JsonpUtils.objectParser(object, mapper);
}
member = memberAndParser.getKey();
// Parse the buffered parser
parser = memberAndParser.getValue();

if (member == null) {
member = fallbackObjectMember;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package co.elastic.clients.elasticsearch.model;

import co.elastic.clients.elasticsearch._types.ErrorCause;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.GeoLocation;
import co.elastic.clients.elasticsearch._types.GeoShapeRelation;
Expand All @@ -30,6 +31,7 @@
import co.elastic.clients.elasticsearch._types.query_dsl.ShapeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.connector.UpdateIndexNameRequest;
import co.elastic.clients.elasticsearch.core.rank_eval.RankEvalQuery;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.testkit.ModelTestCase;
import co.elastic.clients.util.MapBuilder;
Expand Down Expand Up @@ -134,7 +136,6 @@ public void testAdditionalPropertyOnContainer() {
}
}


@Test
public void testAdditionalProperties() {
// Check that additional property map is initialized even if not set explicitly
Expand All @@ -161,7 +162,7 @@ public void testAdditionalProperties() {
}

@Test
public void testShortcutProperty() {
public void testPrimitiveShortcutProperty() {

// All-in-one: a variant, wrapping a single-key dictionary with a shortcut property
String json = "{\"term\":{\"some-field\":\"some-value\"}}";
Expand All @@ -171,6 +172,43 @@ public void testShortcutProperty() {
assertEquals("some-value", q.term().value().stringValue());
}

@Test
public void testEnumShortcutProperty() {

SortOptions so = fromJson("{\"foo\":{\"order\":\"asc\"}}", SortOptions.class);

assertEquals("foo", so.field().field());
assertEquals(SortOrder.Asc, so.field().order());

so = fromJson("{\"foo\":\"asc\"}", SortOptions.class);

assertEquals("foo", so.field().field());
assertEquals(SortOrder.Asc, so.field().order());
}

@Test
public void testObjectShortcutProperty() {

// Standard form
RankEvalQuery req = fromJson("{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}}}", RankEvalQuery.class);

assertEquals("foo", req.query().term().field());
assertEquals("bar", req.query().term().value().stringValue());

// Shortcut form
req = fromJson("{\"term\":{\"foo\":{\"value\":\"bar\"}}}", RankEvalQuery.class);

assertEquals("foo", req.query().term().field());
assertEquals("bar", req.query().term().value().stringValue());

// Nested shortcuts
req = fromJson("{\"term\":{\"foo\":\"bar\"}}", RankEvalQuery.class);

assertEquals("foo", req.query().term().field());
assertEquals("bar", req.query().term().value().stringValue());

}

@Test
public void testWithNull() {

Expand Down

0 comments on commit f1c76cb

Please sign in to comment.