Skip to content

Commit 1722595

Browse files
committed
Allow requests to be serialized as nothing instead of an empty object
1 parent 9792932 commit 1722595

File tree

6 files changed

+346
-8
lines changed

6 files changed

+346
-8
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.json;
21+
22+
import jakarta.json.JsonValue;
23+
import jakarta.json.stream.JsonGenerator;
24+
25+
import java.math.BigDecimal;
26+
import java.math.BigInteger;
27+
28+
/**
29+
* A JSON generator that delegates to another generator.
30+
* <p>
31+
* All convenience methods that accept a property name and an event (value, start object, start array) call separately
32+
* {@link #writeKey(String)} and the same method without the key name. This is meant to facilitate overloading
33+
* of methods.
34+
*/
35+
public class DelegatingJsonGenerator implements JsonGenerator {
36+
protected final JsonGenerator generator;
37+
38+
public DelegatingJsonGenerator(JsonGenerator generator) {
39+
this.generator = generator;
40+
}
41+
42+
public JsonGenerator unwrap() {
43+
return generator;
44+
};
45+
46+
@Override
47+
public JsonGenerator writeStartObject() {
48+
generator.writeStartObject();
49+
return this;
50+
}
51+
52+
@Override
53+
public JsonGenerator writeKey(String s) {
54+
generator.writeKey(s);
55+
return this;
56+
}
57+
58+
@Override
59+
public JsonGenerator writeStartArray() {
60+
generator.writeStartArray();
61+
return this;
62+
}
63+
64+
@Override
65+
public JsonGenerator writeEnd() {
66+
generator.writeEnd();
67+
return this;
68+
}
69+
70+
@Override
71+
public JsonGenerator write(JsonValue jsonValue) {
72+
generator.write(jsonValue);
73+
return this;
74+
}
75+
76+
@Override
77+
public JsonGenerator write(String s) {
78+
generator.write(s);
79+
return this;
80+
}
81+
82+
@Override
83+
public JsonGenerator write(BigDecimal bigDecimal) {
84+
generator.write(bigDecimal);
85+
return this;
86+
}
87+
88+
@Override
89+
public JsonGenerator write(BigInteger bigInteger) {
90+
generator.write(bigInteger);
91+
return this;
92+
}
93+
94+
@Override
95+
public JsonGenerator write(int i) {
96+
generator.write(i);
97+
return this;
98+
}
99+
100+
@Override
101+
public JsonGenerator write(long l) {
102+
generator.write(l);
103+
return this;
104+
}
105+
106+
@Override
107+
public JsonGenerator write(double v) {
108+
generator.write(v);
109+
return this;
110+
}
111+
112+
@Override
113+
public JsonGenerator write(boolean b) {
114+
generator.write(b);
115+
return this;
116+
}
117+
118+
@Override
119+
public JsonGenerator writeNull() {
120+
generator.writeNull();
121+
return this;
122+
}
123+
124+
@Override
125+
public void close() {
126+
generator.close();
127+
}
128+
129+
@Override
130+
public void flush() {
131+
generator.flush();
132+
}
133+
134+
//----- Convenience key+value methods
135+
136+
@Override
137+
public final JsonGenerator writeStartObject(String s) {
138+
this.writeKey(s);
139+
return this.writeStartObject();
140+
}
141+
142+
@Override
143+
public final JsonGenerator writeStartArray(String s) {
144+
this.writeKey(s);
145+
return this.writeStartArray();
146+
}
147+
148+
@Override
149+
public final JsonGenerator write(String s, JsonValue jsonValue) {
150+
this.writeKey(s);
151+
return this.write(jsonValue);
152+
}
153+
154+
@Override
155+
public final JsonGenerator write(String s, String s1) {
156+
this.writeKey(s);
157+
return this.write(s1);
158+
}
159+
160+
@Override
161+
public final JsonGenerator write(String s, BigInteger bigInteger) {
162+
this.writeKey(s);
163+
return this.write(bigInteger);
164+
}
165+
166+
@Override
167+
public final JsonGenerator write(String s, BigDecimal bigDecimal) {
168+
this.writeKey(s);
169+
return this.write(bigDecimal);
170+
}
171+
172+
@Override
173+
public final JsonGenerator write(String s, int i) {
174+
this.writeKey(s);
175+
return this.write(i);
176+
}
177+
178+
@Override
179+
public final JsonGenerator write(String s, long l) {
180+
this.writeKey(s);
181+
return this.write(l);
182+
}
183+
184+
@Override
185+
public final JsonGenerator write(String s, double v) {
186+
this.writeKey(s);
187+
return this.write(v);
188+
}
189+
190+
@Override
191+
public final JsonGenerator write(String s, boolean b) {
192+
this.writeKey(s);
193+
return this.write(b);
194+
}
195+
196+
@Override
197+
public final JsonGenerator writeNull(String s) {
198+
this.writeKey(s);
199+
return this.writeNull();
200+
}
201+
}

java-client/src/main/java/co/elastic/clients/json/jackson/JacksonJsonpMapper.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import co.elastic.clients.json.BufferingJsonGenerator;
2323
import co.elastic.clients.json.BufferingJsonpMapper;
24+
import co.elastic.clients.json.DelegatingJsonGenerator;
2425
import co.elastic.clients.json.JsonpDeserializer;
2526
import co.elastic.clients.json.JsonpDeserializerBase;
2627
import co.elastic.clients.json.JsonpMapper;
@@ -95,16 +96,23 @@ protected <T> JsonpDeserializer<T> getDefaultDeserializer(Type type) {
9596
@Override
9697
public <T> void serialize(T value, JsonGenerator generator) {
9798

98-
if (!(generator instanceof JacksonJsonpGenerator)) {
99-
throw new IllegalArgumentException("Jackson's ObjectMapper can only be used with the JacksonJsonpProvider");
100-
}
101-
10299
JsonpSerializer<T> serializer = findSerializer(value);
103100
if (serializer != null) {
104101
serializer.serialize(value, generator, this);
105102
return;
106103
}
107104

105+
// Delegating generators are used in higher levels of serialization (e.g. filter empty top-level objects).
106+
// At this point the object is not a JsonpSerializable and we can assume we're in a nested property holding
107+
// a user-provided type and can unwrap to find the underlying non-delegating generator.
108+
while (generator instanceof DelegatingJsonGenerator) {
109+
generator = ((DelegatingJsonGenerator) generator).unwrap();
110+
}
111+
112+
if (!(generator instanceof JacksonJsonpGenerator)) {
113+
throw new IllegalArgumentException("Jackson's ObjectMapper can only be used with the JacksonJsonpProvider");
114+
}
115+
108116
com.fasterxml.jackson.core.JsonGenerator jkGenerator = ((JacksonJsonpGenerator)generator).jacksonGenerator();
109117
try {
110118
objectMapper.writeValue(jkGenerator, value);

java-client/src/main/java/co/elastic/clients/transport/ElasticsearchTransportBase.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
2323
import co.elastic.clients.elasticsearch._types.ErrorResponse;
24+
import co.elastic.clients.json.DelegatingJsonGenerator;
2425
import co.elastic.clients.json.JsonpDeserializer;
2526
import co.elastic.clients.json.JsonpMapper;
2627
import co.elastic.clients.json.NdJsonpSerializable;
@@ -260,8 +261,10 @@ private <RequestT, ResponseT, ErrorT> TransportHttpClient.Request prepareTranspo
260261
JsonGenerator generator = mapper.jsonProvider().createGenerator(baos);
261262
mapper.serialize(body, generator);
262263
generator.close();
263-
bodyBuffers = Collections.singletonList(baos.asByteBuffer());
264-
headers = JsonContentTypeHeaders;
264+
if (baos.size() > 0) {
265+
bodyBuffers = Collections.singletonList(baos.asByteBuffer());
266+
headers = JsonContentTypeHeaders;
267+
}
265268
}
266269
}
267270

java-client/src/main/java/co/elastic/clients/transport/endpoints/EndpointBase.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121

2222
import co.elastic.clients.elasticsearch._types.ErrorCause;
2323
import co.elastic.clients.elasticsearch._types.ErrorResponse;
24+
import co.elastic.clients.json.DelegatingJsonGenerator;
2425
import co.elastic.clients.json.JsonpDeserializer;
2526
import co.elastic.clients.json.JsonpDeserializerBase;
2627
import co.elastic.clients.json.JsonpMapper;
28+
import co.elastic.clients.json.JsonpSerializable;
2729
import co.elastic.clients.json.JsonpUtils;
2830
import co.elastic.clients.transport.Endpoint;
31+
import jakarta.json.stream.JsonGenerator;
2932
import jakarta.json.stream.JsonParser;
3033

3134
import javax.annotation.Nullable;
@@ -69,6 +72,58 @@ static <T, U> Function<T, U> returnSelf() {
6972
return (Function<T, U>) RETURN_SELF;
7073
}
7174

75+
/**
76+
* Wraps a function's result with a serializable object that will serialize to nothing if the wrapped
77+
* object's serialization has no property, i.e. it will either produce an empty object or nothing.
78+
*/
79+
public static <T, U extends JsonpSerializable> Function<T, Object> nonEmptyJsonObject(Function<T, U> getter) {
80+
return (x -> x == null ? null : new NonEmptySerializable(getter.apply(x)));
81+
}
82+
83+
private static final class NonEmptySerializable implements JsonpSerializable {
84+
private final Object value;
85+
86+
public NonEmptySerializable(Object value) {
87+
this.value = value;
88+
}
89+
90+
@Override
91+
public void serialize(JsonGenerator generator, JsonpMapper mapper) {
92+
// Track the first property to start the top-level object, and end it if needed in close()
93+
JsonGenerator filter = new DelegatingJsonGenerator(generator) {
94+
boolean gotKey = false;
95+
96+
@Override
97+
public JsonGenerator writeStartObject() {
98+
if (gotKey) {
99+
super.writeStartObject();
100+
}
101+
return this;
102+
}
103+
104+
@Override
105+
public JsonGenerator writeKey(String s) {
106+
if (!gotKey) {
107+
gotKey = true;
108+
super.writeStartObject();
109+
}
110+
super.writeKey(s);
111+
return this;
112+
}
113+
114+
@Override
115+
public JsonGenerator writeEnd() {
116+
if (gotKey) {
117+
super.writeEnd();
118+
}
119+
return this;
120+
}
121+
};
122+
123+
mapper.serialize(value, filter);
124+
}
125+
}
126+
72127
protected final String id;
73128
protected final Function<RequestT, String> method;
74129
protected final Function<RequestT, String> requestUrl;

java-client/src/main/java/co/elastic/clients/transport/endpoints/SimpleEndpoint.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public SimpleEndpoint(
5252
Function<RequestT, Map<String, String>> pathParameters,
5353
Function<RequestT, Map<String, String>> queryParameters,
5454
Function<RequestT, Map<String, String>> headers,
55-
boolean hasResponseBody,
55+
boolean hasRequestBody,
5656
JsonpDeserializer<ResponseT> responseParser
5757
) {
5858
this(
@@ -62,7 +62,7 @@ public SimpleEndpoint(
6262
pathParameters,
6363
queryParameters,
6464
headers,
65-
hasResponseBody ? returnSelf() : returnNull(),
65+
hasRequestBody ? returnSelf() : returnNull(),
6666
responseParser
6767
);
6868
}

0 commit comments

Comments
 (0)