This is an automated email from the ASF dual-hosted git repository.
robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
The following commit(s) were added to refs/heads/master by this push:
new 8e0ffc7f9b Add field filtering documentation to Spring Boot userguide
8e0ffc7f9b is described below
commit 8e0ffc7f9b6450d24711123a91f8aa74648a5997
Author: Robert Lazarski <[email protected]>
AuthorDate: Tue Apr 21 17:57:10 2026 -1000
Add field filtering documentation to Spring Boot userguide
Documents the ?fields= query parameter with configuration, usage
examples (flat and multi-level dot-notation), a 97% payload reduction
example, competitive comparison table (Spring, GraphQL, gRPC, Django,
FastAPI), and Axis2/C parity notes.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
src/site/xdoc/docs/json-springboot-userguide.xml | 165 +----------------------
src/site/xdoc/docs/json-streaming-formatter.xml | 106 ++++++++++++---
2 files changed, 94 insertions(+), 177 deletions(-)
diff --git a/src/site/xdoc/docs/json-springboot-userguide.xml
b/src/site/xdoc/docs/json-springboot-userguide.xml
index 68302aeb4e..907e0d7006 100644
--- a/src/site/xdoc/docs/json-springboot-userguide.xml
+++ b/src/site/xdoc/docs/json-springboot-userguide.xml
@@ -511,167 +511,10 @@ curl -v -H "Authorization: Bearer 95104Rn2I2oEATfuI90N" \
<h2>Response Field Filtering</h2>
-<p>Axis2/Java supports <code>?fields=</code> query parameter filtering that
-reduces response payload size by serializing only the requested fields.
-Filtering happens during streaming serialization — excluded fields are
-never buffered, never written to the wire. No service-side code changes
-are required.</p>
-
-<h3>Configuration</h3>
-
-<p>In <code>axis2.xml</code>, wrap the streaming formatter with
-<code>FieldFilteringMessageFormatter</code>:</p>
-
-<pre>
-<messageFormatter contentType="application/json"
- class="org.apache.axis2.json.streaming.FieldFilteringMessageFormatter">
- <parameter
name="delegateFormatter">org.apache.axis2.json.streaming.MoshiStreamingMessageFormatter</parameter>
-</messageFormatter>
-</pre>
-
-<p>Without <code>?fields=</code> in the request, the formatter is a
zero-overhead
-pass-through to the delegate.</p>
-
-<h3>Usage</h3>
-
-<p><strong>Flat filtering</strong> — select top-level fields:</p>
-<pre>
-GET /services/MyService?fields=status,result
-</pre>
-
-<p><strong>Dot-notation</strong> — filter inside nested objects and
collections:</p>
-<pre>
-GET /services/MyService?fields=status,data.records.id,data.records.name
-</pre>
-
-<p>This walks three levels: keep <code>data</code> at top level, keep
-<code>records</code> inside it, keep only <code>id</code> and <code>name</code>
-inside each element of the <code>records</code> array. Multi-level dot-notation
-works on POJOs, Maps, and Collections — including
-<code>Map<String, Object></code> containing
-<code>List<Map<String, Object>></code>, which is the structure
-produced by JSON-RPC services that parse JSON into Java Collections.</p>
-
-<h3>Why Multi-Level Dots: A Java Collections Example</h3>
-
-<p>Consider a service that returns a response POJO where the heavy data
-is inside a <code>Map<String, Object></code>. This is common when the
-service parses JSON from a backend (e.g., a calculation engine) into
-Java Collections rather than typed POJOs:</p>
-
-<pre>
-// The response POJO — what the Axis2 service method returns
-public class ServiceResponse {
- private String status;
- private long responseTimeMs;
- private Map<String, Object> data; // parsed from backend JSON
- // getters, setters
-}
-</pre>
-
-<p>The <code>data</code> map contains multiple keys. One of them holds a
-list of records — each record is itself a map with 100+ fields:</p>
-
-<pre>
-// What "data" looks like at runtime (after JSON parsing):
-//
-// data = {
-// "records" -> List<Map<String, Object>> <-- 100+ fields
each
-// "metadata" -> Map<String, Object>
-// "diagnostics" -> Map<String, Object>
-// }
-//
-// Each record in the list:
-// { "id": "item-1", "name": "Widget A", "price": 19.99,
-// "category": "HARDWARE", ... 96 more fields ... }
-</pre>
-
-<p>The Java path from the response root to a record field is three levels
deep:</p>
-
-<pre>
-response.getData() // Map<String, Object> = "data"
- .get("records") // List<Map> =
"data.records"
- .get(0).get("id") // Object =
"data.records.id"
-</pre>
-
-<p>That maps directly to the <code>?fields=</code> syntax:</p>
-
-<pre>
-?fields=status,data.records.id,data.records.name
- | | | |
- | | | +-- 3rd dot level: key inside each List element
- | | +------------- 2nd dot level: key inside the data Map
- | +----------------------- 1st dot level: field on the response POJO
- +------------------------------- top-level POJO field (no dots)
-</pre>
-
-<h3>Example: Before and After</h3>
-
-<p>A service returns a 5MB response with 127 fields per record:</p>
-<pre>
-{"response": {
- "status": "SUCCESS",
- "data": {
- "records": [
- {"id": "item-1", "name": "Widget A", ... 125 more fields ...},
- {"id": "item-2", "name": "Widget B", ... 125 more fields ...}
- ],
- "metadata": {...},
- "diagnostics": {...}
- }
-}}
-</pre>
-
-<p>With <code>?fields=status,data.records.id</code>:</p>
-<pre>
-{"response": {
- "status": "SUCCESS",
- "data": {
- "records": [
- {"id": "item-1"},
- {"id": "item-2"}
- ]
- }
-}}
-</pre>
-
-<p>The metadata, diagnostics, and 126 unused fields per record are never
-serialized. A 5MB response becomes ~150KB — a 97% reduction.</p>
-
-<h3>Competitive Context</h3>
-
-<p>No other Java or Python JSON web services framework ships recursive
-multi-level field filtering that operates on runtime Maps and Collections
-from a query parameter:</p>
-
-<table>
-<tr><th>Framework</th><th>Flat fields</th><th>Multi-level dots</th><th>Works
on Map/List</th><th>No code changes</th></tr>
-<tr><td><strong>Axis2/Java</strong></td><td><strong>Yes</strong></td><td><strong>Yes</strong></td><td><strong>Yes</strong></td><td><strong>Yes</strong></td></tr>
-<tr><td>Spring +
Jackson</td><td>Manual</td><td>No</td><td>No</td><td>No</td></tr>
-<tr><td>GraphQL</td><td colspan="4">Full field selection, but requires a
different API architecture</td></tr>
-<tr><td>gRPC FieldMask</td><td>Yes</td><td>Yes</td><td>No (Protobuf
only)</td><td>Yes</td></tr>
-<tr><td>Django REST + flex-fields</td><td>Yes</td><td>Partial (1
level)</td><td>No</td><td>No</td></tr>
-<tr><td>FastAPI /
Pydantic</td><td>Manual</td><td>No</td><td>No</td><td>No</td></tr>
-</table>
-
-<p>The closest competitor is gRPC's FieldMask, which supports dot-notation
-paths but requires Protocol Buffers — it cannot help existing JSON-RPC
-services. GraphQL provides full field selection but requires a completely
-different API contract. Spring and Django require custom serializer code
-for any field filtering beyond flat, annotated POJOs.</p>
-
-<p>Axis2/Java achieves this without annotations, schema changes, or new
-dependencies. The service returns whatever POJO or Map it already returns.
-The formatter intercepts at the serialization layer, filters during
-streaming, and the client receives only the fields it asked for.</p>
-
-<h3>Parity with Axis2/C</h3>
-
-<p>The Axis2/C implementation supports single-level dot-notation
-(<code>?fields=status,results.id</code>). Multi-level dot-notation is
-an Axis2/Java extension. Axis2/C uses json-c parse-and-delete;
-Axis2/Java uses reflection-based selective serialization. Both
-approaches produce identical filtered output for single-level queries.</p>
+<p>See the <a href="json-streaming-formatter.html">Streaming JSON Formatter</a>
+guide for complete documentation on <code>?fields=</code> query parameter
+filtering, including multi-level dot-notation for nested Maps and Collections,
+Java POJO examples, and competitive context.</p>
</body>
</html>
diff --git a/src/site/xdoc/docs/json-streaming-formatter.xml
b/src/site/xdoc/docs/json-streaming-formatter.xml
index 8b296180cd..b4f5529f9d 100644
--- a/src/site/xdoc/docs/json-streaming-formatter.xml
+++ b/src/site/xdoc/docs/json-streaming-formatter.xml
@@ -101,22 +101,96 @@
<subsection name="Field Selection (?fields=)">
<p>When using <code>FieldFilteringMessageFormatter</code>, callers can
- reduce response payload size by specifying which top-level fields to
- include. This is useful for AI agents (MCP tools) and API consumers
- that need only a subset of the response fields.</p>
-<source><![CDATA[# Only return status and portfolioVariance fields
-curl -sk --http2 \
-
"https://host/services/FinancialBenchmarkService?fields=status,portfolioVariance"
\
- -H "Content-Type: application/json" \
- -d '{"portfolioVariance":[{"arg0":{...}}]}'
-
-# Response: {"response":{"status":"SUCCESS","portfolioVariance":0.0245}}
-# (other fields like calcTimeUs, annualizedVolatility, etc. are
omitted)]]></source>
- <p>Field filtering happens during serialization — non-selected fields
- are never serialized, never buffered, and never written to the wire.
- The streaming pipeline (Moshi → Okio → FlushingOutputStream → HTTP/2
- DATA frames) is preserved. When no <code>fields</code> parameter is
- present, the formatter delegates directly with zero overhead.</p>
+ reduce response payload size by specifying which fields to include.
+ This is useful for AI agents (MCP tools), mobile clients, and API
+ consumers that need only a subset of the response.</p>
+
+ <p><b>Flat filtering</b> — select top-level response fields:</p>
+<source><![CDATA[GET /services/MyService?fields=status,result
+# Response: {"response":{"status":"SUCCESS","result":0.0245}}
+# (other top-level fields omitted)]]></source>
+
+ <p><b>Multi-level dot-notation</b> — filter inside nested objects and
+ collections. This is the key feature for services that return large
+ nested data structures.</p>
+
+ <p>Consider a service that returns a response POJO where the heavy
+ data lives inside a <code>Map<String, Object></code> — common
+ when the service parses JSON from a backend into Java Collections
+ rather than typed POJOs:</p>
+
+<source><![CDATA[// The response POJO — what the Axis2 service method returns
+public class ServiceResponse {
+ private String status;
+ private long responseTimeMs;
+ private Map<String, Object> data; // parsed from backend JSON
+}
+
+// At runtime, "data" contains:
+// "records" -> List<Map<String, Object>> (100+ fields per element)
+// "metadata" -> Map<String, Object>
+// "diagnostics" -> Map<String, Object>]]></source>
+
+ <p>The Java path from the response root to a record field maps
+ directly to the <code>?fields=</code> syntax:</p>
+
+<source><![CDATA[response.getData() // Map<String, Object> =
"data"
+ .get("records") // List<Map> = "data.records"
+ .get(0).get("id") // Object = "data.records.id"
+
+?fields=status,data.records.id,data.records.name
+ | | | |
+ | | | +-- 3rd level: key inside each List element
+ | | +------------- 2nd level: key inside the data Map
+ | +----------------------- 1st level: field on the response POJO
+ +------------------------------- top-level POJO field (no dots)]]></source>
+
+ <p><b>Before</b> (5MB, 127 fields per record):</p>
+<source><![CDATA[{"response": {
+ "status": "SUCCESS",
+ "data": {
+ "records": [
+ {"id":"item-1", "name":"Widget A", ... 125 more fields ...},
+ {"id":"item-2", "name":"Widget B", ... 125 more fields ...}
+ ],
+ "metadata": {...},
+ "diagnostics": {...}
+ }
+}}]]></source>
+
+ <p><b>After</b> <code>?fields=status,data.records.id</code> (~150KB,
97% reduction):</p>
+<source><![CDATA[{"response": {
+ "status": "SUCCESS",
+ "data": {
+ "records": [
+ {"id":"item-1"},
+ {"id":"item-2"}
+ ]
+ }
+}}]]></source>
+
+ <p>Filtering happens during serialization — excluded fields are never
+ serialized, never buffered, never written to the wire. The streaming
+ pipeline is preserved end-to-end. When no <code>fields</code> parameter
+ is present, the formatter delegates directly with zero overhead.</p>
+
+ <p>Multi-level dot-notation works on POJOs, Maps, and Collections —
+ including <code>Map<String, Object></code> containing
+ <code>List<Map<String, Object>></code>. Both Moshi and GSON
+ formatters support the same filtering behavior.</p>
+
+ <p><b>Competitive context:</b> No other Java or Python JSON web
services
+ framework ships recursive multi-level field filtering that operates on
+ runtime Maps and Collections from a query parameter. The closest
+ comparable is gRPC FieldMask (Protobuf only) and GraphQL (requires a
+ different API architecture). Spring + Jackson, Django REST, and FastAPI
+ all require custom serializer code for nested field selection.
+ Axis2/Java achieves this with zero service-side code changes.</p>
+
+ <p><b>Limitation:</b> Field names containing a literal dot character
+ cannot be selected, as the dot is always interpreted as a nesting
+ delimiter. The Axis2/C implementation supports single-level
+ dot-notation only; multi-level is an Axis2/Java extension.</p>
</subsection>
<subsection name="Flush Interval Tuning (services.xml)">