codeconsole commented on PR #16237:
URL: https://github.com/apache/grails-core/pull/16237#issuecomment-6269489699
@matrei Round 6 is addressed on d10a0db1fd (seven commits after the 9.0.x
merge 81abe17b47). The module tests, Checkstyle and CodeNarc pass for the
modules touched.
**Blocking**
1. 834020b655, b814877d97. `DefaultJsonRenderer` and `DefaultXmlRenderer`
ask `getEffectiveIncludes`/`getEffectiveExcludes` for the lists; `JsonRenderer`
and `XmlRenderer` override them. They feed both the projection and the decision
to stay on the legacy converter. `ControllerJsonSerializationSpec` runs your
`Account` case (`{"user":"ada"}` with the opt-in on), and
`SpringXmlRendererSpec` runs the XML one.
**Should fix**
2. c0566dc453. Configured names are compared with `equalsIgnoreCase` in both
`find`s, and the matched type takes the configured name.
`AcceptHeaderParserSpec` and `RequestAndResponseMimeTypesApiSpec` use
`application/vnd.Acme+json` as `Accept` and `Content-Type`, with `withFormat`.
Section 12 also covers `Content-Type` now. `Accept: *` stays unmatched as in
Grails 8, because Spring reads it as `*/*`.
3. 1ea68671b0. `writeAssociationReference` unwraps only when it has no
identifier. The spec uses an `EntityProxyHandler` whose unwrap throws, and
asserts no unwrap happens.
4. acc8c378bb, 4ed91133e5. The plugin's factory bean is registered with
`fallback()`, so an application bean of that type wins whatever its name (one
named `validationProblemDetailFactory` only since `2e2fcd6bc7`, below), and the
bean named `validationProblemDetailFactory`, which controllers autowire by
name, resolves to the same one. Section 10 shows `new
ValidationProblemDetailFactory(true, messageSource)`.
5. b814877d97. `DefaultXmlRenderer` skips the view check for `Errors`. The
spec asserts the locator is never asked and the status is 422.
6. d436a7b2f3. `render(Map)` skips the named-JSON branch for a `JSONElement`.
7. c0566dc453. The bean method is `static` and takes an
`ObjectProvider<MimeUtility>`; the configurer builds its strategy on first use.
`MimeTypesConfigurationSpec` runs your repro (`@EnableWebMvc`, a
`MimeTypeProvider` that takes a `LocaleResolver`) with circular references
disallowed, and fails when the configurer builds its strategy eagerly
(3aa15e5590).
8. d10a0db1fd. `vndError.adoc` shows the legacy body as the Grails 9 default
and the problem as the opt-in and Grails 10.
9. d10a0db1fd. Blank line added, link added, and the chapter says its shapes
are the Grails converters', not the Spring opt-ins'.
**Minor**
- `respond problem, jsonConfiguration:` answers as a problem (834020b655).
- The fallback warning says why when the setting is already on, and
`missingConvertersReported` is shared per registry (834020b655).
- Section 4 lists `pretty.print` and `date` as settings Jackson does not
read, and says bean excludes apply (d10a0db1fd). `default.deep` applies since
the #16414 merge.
- A `ProblemDetail` with no converters logs a specific warning, and section
10 is qualified (834020b655, d10a0db1fd).
- A narrowed proxy in a collection keeps its excludes (1ea68671b0).
- Without `grails-xml`, section 3 lists the outcomes. I left
`RestResponder`'s 404 for errors as it is, since it predates the PR.
- `grails-xml` has `runtimeOnly project(':grails-databinding')` (b814877d97).
- The XML warning now says "scheduled for removal", which matches the
classes that are annotated (b814877d97).
- The strategy no longer implements `ContentNegotiationStrategy`, and
`resolveMediaTypes`/`resolveMimeTypes` are gone. Their tests were replaced by
`response.format` features for the `format` parameter, `RESPONSE_FORMAT` and a
bare `*` (c0566dc453).
- The XHR check is exact (c0566dc453). A context without the configurer,
such as a hand-built test context, builds the strategy for the request;
production always has the configurer, and no static state is kept (a1f6e39184).
- Nits: the `MessageSource` import, the stale feature names,
`RespondJsonMarshallerRegistererSpec` on a nested `@Configuration`, the HAL and
Atom wording, and the stray blank lines.
**Earlier rounds**
- R4 `ObjectProvider` resolution per response: a found locator or registry
is kept, one registered later is still found.
- R4 autowired properties: section 3 says `groovyPageLocator` and
`rendererRegistry` are no longer autowired.
- R4 manual provider in `XmlGrailsPlugin`: kept, since the plugin spec's
bare bean factory does not process `@Autowired`.
- R5 `grails-openapi`: `openApi.adoc` says its document does not follow
`json.spring` or `xml.spring`.
---
**Since that reply.** @matrei @jdaugherty After #16414 merged I reworked how
domain classes, beans and the JSON converter share Jackson. This is on the
branch up to `8c917ae3fd`; the upgrade guide and the description list each
user-visible change.
**Domain classes.** `respond`, the Boot mapper with
`domain.jackson.enabled`, and `grails.converters.JSON` with `legacy: false` now
write a domain class with Jackson's own bean serializer, shaped by a serializer
modifier, instead of the hand-written `DomainClassSerializer` (`711bbfe05f`).
`@JsonInclude`, `@JsonView`, `@JsonFormat`, mix-ins, filters and
`@JsonPropertyOrder` apply to domain properties; the converter follows Jackson
for them, including `@JsonProperty` names and properties that
`@JsonIgnoreProperties` names. The converter hands each instance its own
rendering (includes and excludes, `asShortObject` hooks) through a writer
attribute and a small `DomainClassRenderer` interface that
`DomainClassMarshaller` implements (`b7ee928dc8`, `3afdaf1fd6`). The hibernate5
example's `JsonMapperConverterSpec` passes.
**Cycles.** The domain rendering now reads
`grails.converters.json.circular.reference.behaviour` wherever a domain object
is written, so the same setting means the same thing in `respond`, the
converter and the Boot mapper (`a32ce26b03`). With
`grails.converters.json.domain.jackson.enabled`, a plain Spring MVC controller
that returns a deep, cyclic domain graph gets the configured form (`_ref` by
default) instead of a bare `{"id": …}`.
**The `legacy: false` converter**, in separate commits:
- The nested-value frame lives on the serialization context, not in a
thread-local (`78b04ed334`).
- Jackson writes collections, maps, arrays, records and `Optional` values;
the converter still renders each value in them, so registered marshallers
apply, and a collection or map that contains itself is found as before
(`32c1a6b850`). `RecordMarshaller` and `OptionalMarshaller` are gone.
- Jackson writes enums and beans too. `ConverterJsonModule` adds the
converter's rules: enums by name (a `Month` keeps its number), per-class
includes and excludes, and `@PersistenceMethod`/`@ControllerMethod` methods
left out (`9e58e1ae48`). The Grails bean marshallers stay for `legacy: true`
and for anyone who registers them.
- `render errors as JSON` writes validation errors as `respond` does
(object, field, codes, message), without the rejected values, which can be
passwords or tokens. The Grails 8 errors marshaller is registered only while
`legacy` is `true` (`22f9ebb371`).
- `JSON.parse` and `request.JSON` parse with the application's Jackson
mapper, so the `spring.jackson.json.read.*` properties, such as
`spring.jackson.json.read.allow-java-comments`, decide what is accepted, and
strict JSON, with nothing after the value, is the default. The
`JSONObject`/`JSONArray` results and the number types are unchanged. `new
JSONObject(String)` and `new JSONArray(String)` always use Grails' own parser
(`8c917ae3fd`).
**Output changes under `legacy: false`:** Jackson annotations apply to
beans, bean properties follow Jackson's order, a map with a null key fails, a
`Map.Entry` is `{"k":1}`, validation errors have no rejected values, and
`JSON.parse` rejects the lenient syntax (single quotes, comments, unquoted
values, trailing commas, `new Date(...)`) unless the Jackson parser features
allow some of it. `legacy: true` is unchanged.
---
**Self-review, `8c917ae3fd`..`a30f1d48bc`.** I then reviewed the whole PR,
with read-only reviewers over several passes and each finding checked in the
code. Each fix below is in its own commit, with a test that fails without it.
Domain classes and the JSON converter:
- A domain class id ignored `@JsonView`, `@JsonSerialize`,
`@JsonIgnoreProperties` and mix-ins. It is now written by the property's own
writer, which another module may replace, with the value the rendering gives it
through `extractValue` and the property's `@JsonInclude`. An unsaved instance
has no id unless the rendering gives it one (`991828c0a7`, `c52c7a6028`,
`06b671887b`).
- The version and class name are written in the views the class names
(`bb086af823`). A mix-in of the class, a superclass, an interface or `Object`
can leave out the version (`4448c3750c`, `1a8fddef1e`).
- Deep associations lost their property annotations, in the mappers
(`3b468d19aa`) and in the converter (`b03d420d44`). A deep to-many association
declared with a type that writes type ids writes its instances with theirs
(`0eb9fbcc46`), and so does one whose property's `@JsonTypeInfo` names them,
which every way of writing JSON left out (`6181ccf43d`).
- `@JsonIdentityInfo` was overridden by the cycle tracking, in the mapper
and in the converter (`d78b310fae`, `1ee407304a`). A value that a registered
marshaller, or a `JsonMapperValueMarshaller` subclass, writes itself came out
as `{}` when reached again; it is a circular reference again (`8c60604cb3`,
`46936cdad3`).
- Under `legacy: false`, the converter's includes and excludes matched only
a property's bean name, so `respond` could name a property by the name it is
written with on one setting of `grails.web.rendering.json.spring` and not on
the other. They match either name now, for beans and domain classes; a
`DomainClassRendering` can decide by the written name with a new three-argument
`includes`, which the class name and the version go through too (`d1c699e9b4`,
`e9298a6ac3`).
- The includes and excludes a converter is given for a proxy, which
`respond`'s legacy path passes it, were kept for the class of the proxy, which
the converter never writes, so `respond(proxy, excludes: ['password'])` wrote
the password, under either setting of `legacy`, as Grails 8 did. They now apply
to the instance written in its place, wherever it is unwrapped, a deep
association and a `DomainClassMarshaller` that unwraps it with its own handler
included, for one render, and an object being written keeps the includes and
excludes it started with, where those of a proxy came to filter the properties
after it (`753da5e359`, `9cf08baac9`, `699a501d70`, `719ec47db9`).
- On MongoDB, Neo4j and other non-Hibernate datastores, a lazy proxy threw
`IllegalStateException`, because only Hibernate registers a proxy handler. A
GORM proxy is now unwrapped by itself, and a reference reads its key without
loading it (`b89b781d98`). A proxy of a deleted row is written as `null`, also
with a projection or through a deep association with `@JsonTypeInfo`
(`79100ab172`, `92e5307bca`).
- The converter's mapper layered the Grails modules after the application's,
the opposite of `respond`'s, so its `Errors` serializer beat an application one
(`2be7256a85`).
- Under `legacy: false`, the converter failed on a property annotated
`@JsonFormat(shape = ...)` or `@JsonUnwrapped` and on an enum written with a
type id, and ignored the annotations of a property holding a bean, collection
or map. These now behave as in the JsonMapper (`c0afe21e6b`, `165e0260db`,
`cddd925961`, `b03d420d44`). A `JsonMapperValueMarshaller` subclass is honoured
for a nested single value, such as a UUID in a map (`62c2a5ee3b`, `2c86131f7f`).
- `@JsonIgnoreProperties` on a `@JsonValue` accessor was ignored in
`respond` and for domain classes, so the properties it names were written
(`d043abfceb`).
- The converter and named configurations dropped the default attributes of
the application's mapper (`cb6b9b1d62`), and a named configuration called
`projection` turned projections off (`dd768b8aa9`).
- Under `legacy: false`, a `JsonRenderer` bean of a domain class wrote its
instances with the module's rendering, associations as references and no
version or class name, since the converter cannot read a rendering from the
closure the bean registered its deep marshaller with; it registers a marshaller
of its type that gives the converter that rendering (`2203c20edf`).
- Under `legacy: false`, with several `JsonMapper` beans and none primary,
the converter rendered and parsed with a default mapper, where `respond`, the
registry and data binding fall back to Spring Boot's `jacksonJsonMapper`; it
does too, so `request.JSON` and binding accept the same JSON, as the guide says
(`b9bee01f32`). `grails.converters.json.date: javascript` writes `new
Date(...)`, which is not JSON and which the Jackson parser of `legacy: false`
cannot read back, `response.json` in a unit test included; the combination now
fails at startup, naming both settings (`c85445a9ca`).
- `JSON.use('deep')` is deprecated for removal in favour of the registry,
but no registry configuration could render one write deep.
`NamedJsonConfiguration.deep()` now does, and every registry has a
configuration named `deep` (`aa23842c41`).
- With the `IGNORE` circular reference behaviour, the converter failed on a
property reached again. An element of a list is left out and a property is
`null`, as in the mapper (`a5e809e17e`).
- With `DEFAULT`, a bean that refers to itself failed the converter, under
either setting, as in Grails 8, and was `".."`, which names its owner,
everywhere else; it is `"."` (`32693b0c18`). Under `legacy: false` the
converter counted the `..` of a reference in the objects it was writing, the
mappers in JSON objects and arrays, which differ through an `Optional` or a
type wrapper; it counts as the mappers do (`0cd5188331`), and finds an object
reached again by identity, as Jackson and `respond` do, where an object only
equal to one being written, such as an instance loaded twice, was a reference
(`a343663c50`). The legacy converter, the Grails 9 default, counts, finds and
writes references as Grails 8 did, as a map that a marshaller registered for
maps writes; my count had changed its text too (`10c8a4bb3c`). A `PATH`
reference had its keys in another order in the mappers than in the converter,
and in the converter's under `order-map-entries-by-keys`; all write
`{"ref":…,"class"
:…}`, as Grails 8 did (`acc497189c`, `42b7b7ca21`).
- A Hibernate proxy, which the converter unwraps itself, lost the
annotations of the property it was in, such as `@JsonIgnoreProperties`. The
converter now says, where it unwraps a proxy, which instance stands for it, and
the instance is written with the property's serializer for its class, a
module's wrappers included (`578109dd82`, `dc40154b2b`). A proxy in a
`@JsonUnwrapped` property was written under the property's name (`0654b90de5`),
and a `@JsonUnwrapped` value whose serializer cannot unwrap, such as an enum,
skipped a registered marshaller (`7dcb69249d`).
- Jackson 3.2 rejects a bean that unwraps a property with the name of
another. The mappers of responses and named configurations wrote the name
twice, as in `{"id":5,"id":1}`, and now reject it too (`7ccb36945f`). The JSON
converter keeps writing it: the class name and the version of a domain class
instance it writes depend on the rendering of each write, so the check would
reject beans whose own `version` is never in conflict. A domain class has class
name and version properties only where its rendering may write them, so Spring
Boot's mapper with `domain.jackson.enabled` no longer rejects such a bean
(`74abf07cff`). A named configuration could set the attribute by which the
converter renders each instance, and so leave out the class name and the
version but never write them; it is now rejected (`e546b9e977`).
- With `IGNORE`, a property reached again of a bean written as an array was
left out, which moved the later properties up a position; it is `null`
(`108ea3132d`). A proxy of a deleted row is left out where a property leaves
out empty values (`0fc61895f1`), and the serializer of the class a proxy stands
for is found once (`706e6185eb`).
- A reference written after a `@JsonUnwrapped` property took the unwrapped
value for its owner, so the converter wrote it with the module's rendering
rather than the instance's: a `DomainClassMarshaller` that writes the ids with
a prefix, its `asShortObject` and `JSON.use('deep')` did not apply to it
(`e3c584ecbf`).
- Jackson matches `@JsonIgnoreProperties` against the names it writes, so
under a naming strategy such as `SNAKE_CASE` the `tenantId` GORM ignores on
every multi-tenant domain class was written as `"tenant_id"`. Ignored
properties are now left out by their own names (`d64b5b0bba`).
- The converter wrote an array-shaped domain class instance with `null`
where its rendering wrote no class name or version, `[null,1,null,"first"]`
where `respond` writes `[1,"first"]` (`2050134d24`). A `@JsonAnyGetter` method
of a domain class was written, though only persistent properties are
(`911f85b31d`). The entries of a persistent `Map` property annotated
`@JsonAnyGetter` vanished, or were written as an object under a naming strategy
that renamed the property; they are written in its place, as Jackson writes
them, under every naming strategy, by Jackson's own writer of them, gated, so
that a `@JsonFilter` filters them one by one and an `ObjectNode` is written
(`43353b657d`, `a1f83c3ac3`). On a bean, a response's projection and the
converter's includes and excludes never reached the entries of a
`@JsonAnyGetter`, which Jackson writes with a writer of its own; they do now
(`22ed792f5a`, `57193375df`).
- An associated instance of a class with a serializer of its own, from a
`@JsonValue`, `@JsonSerialize` (a converter included), `JacksonSerializable` or
a module, was a reference of its id, `{"id":1}`, or `{}` unsaved. It is now
written with that serializer as Jackson writes the value of the property, with
the property's `@JsonFormat` and inclusion and the type id its declared type
asks for, since the application's serializers take precedence over the Grails
ones. A `DomainClassMarshaller` that writes references itself still does, as
does any rendering that overrides `writeReference`, and a class whose
serializer Jackson fails to build, such as one that unwraps a property with the
name of one of its own, is still referenced, where my first change failed the
whole response (`7ec84d2b8d`, `d97beda5d7`, `6c16398717`, `3844eed7df`,
`a67150e284`, `52c6a20283`). Written in full, such an instance is left out
where its property leaves out an empty value (`0d7c79379f`), and the elements of
a to-many association get the type ids its own `@JsonTypeInfo` asks for
(`7d2273f1ea`). Under `legacy: false`, the converter dropped the type id of an
instance whose `@JsonValue` gives a string, since it wraps every serializer of
its mapper; Jackson's serializers of natural JSON values are no longer wrapped
(`37c7f00214`). For the same reason it dropped the type ids a property's
`@JsonTypeInfo` gives the elements of its collection, array or map, which
Jackson gives only a serializer it takes for a container one; the wrapper of a
container serializer is one now (`1837af0ba3`).
- `JSON.parse` under `legacy: false` accepted text after the value, and read
`1.5e1` as a `BigDecimal` where Grails' parser gives `Integer`; both now match.
`1e400` is a `BigDecimal` with either parser, where it threw a
`NumberFormatException` as in Grails 8 (`95e80de131`, `66be290fd2`).
- Under `legacy: false`, the converter wrote a number, string or boolean it
rendered on its own, or for a marshaller's `property`, `convertAnother` or
`build`, itself, without the serializer a module of the application gives its
type, such as `ToStringSerializer` for `Long`, which it used inside a map; it
writes such a value with the mapper wherever it writes it (`1d53411271`). It
also ignored `spring.jackson.serialization.wrap-root-value`, which wraps
`respond`'s body; it wraps the value it renders as the mapper does
(`bbafded9b6`). The legacy converter escaped a control character without a
short escape, such as U+001B, with upper-case hexadecimal digits, as Jackson
does, where Grails 8 wrote lower-case ones; it writes Grails 8's text again
(`a2f4037e0a`). A `JsonRenderer` bean's marshaller let a getter's exception
through, where Grails 8's closure reported it as a `ConverterException`, also a
checked one (`59b70f47f5`, `3e57342fc4`). With `PATH` and a wrapped root value,
every r
eference was a step short of the value it repeats; it counts the wrapper, as
`respond` does (`44dfa3fd7d`). Initializing the converters for legacy JSON
without a context kept the mapper an earlier initialization had set, so the
legacy converter could lose Grails 8's escapes (`ab9de44b75`). A `JSONWriter`
wrote a `String` with its generator but a `GString`, a `StreamCharBuffer` or
any other value as text Grails' JSON encoder had escaped, so the same control
character was escaped two ways under `legacy: false` and the mapper's write
features applied to Strings alone; every string is now escaped as a `String` is
(`cc8e5802f3`).
- The legacy converter's mapper kept Jackson's nesting limit of 500 objects
and arrays, so a value Grails 8 wrote failed; it writes as deep as Grails 8 did
(`f6e0e80800`). A value nested deeper than the thread's stack lets the
converter write, such as an object graph without end, threw a
`StackOverflowError` for a map or a collection under either setting; it fails
with a `ConverterException`, as Jackson reports a bean it overflows the stack
writing. The guide says that under `legacy: false` the mapper's limit applies,
and that the converter, which writes each object or array through the mapper
and back, needs more of the stack for each level, so that a value a few hundred
levels deep can exceed a 1 MB thread stack first; it also says that Jackson's
read limits are set on its factory, which a `JsonMapperBuilderCustomizer`
cannot reach (`5e1eac869b`, `114a028e1b`). Under `legacy: false` without an
application `JsonMapper`, as in a unit test other than a web test, the
converter deriv
ed its mapper from the legacy writer's, which has no limit, and so wrote what
the application's mapper refuses; it has Jackson's limits (`1f5468915e`).
`ConverterJsonModule` failed to write validation errors without a message
source, which its Javadoc allows (`18d4724d2c`).
`respond` and the renderers:
- A `ProblemDetail` is written as Spring MVC writes it with Spring Boot's
converters, whatever the settings, with the problem's status: as
`application/problem+xml` for a client whose `Accept` header asks for a problem
in XML, such as `application/problem+xml` or `application/*+xml`, where the
action responds in XML, and as `application/problem+json` for any other, a
client asking for `application/xml` or `text/xml`, a browser, one that accepts
anything and one that names the XML format included, where an XML request got a
bean with a 200 (`f0b4af2680`, `a4243aed3e`; `5871b47fde` had followed Spring
MVC with its XML converter ahead of the JSON one, which is not Boot's order).
Its JSON was written by the Grails mapper, so a domain class instance in a
problem had the Grails shape and a member that refers to itself a `_ref`;
Spring's converter writes it now, as it writes the XML (`0f76fbf8bd`). Without
`jackson-dataformat-xml`, Grails writes the RFC 9457 format Spring's converter
wri
tes, a subclass's properties, binary data in Base64, raw values, plain
decimals and the properties of a bean in no namespace included, with the JDK's
StAX writer (`9d72643374`, `592ee3516c`, `6e65999d15`, `d4f499dd24`). Without
`jackson-dataformat-xml`, which Spring's XML converter needs and `grails-xml`
does not bring, Spring MVC answers a client asking for a problem in XML with
JSON, and `respond` answers it with the XML Grails writes; the docs now say so
rather than claiming Spring's answer (`7bc45030a0`). That XML now matches
Spring's converter for a bean unwrapped with a prefix, which wrote a malformed
element, a property written as an array, `@JsonIncludeProperties` and property
filters, a list a bean holds, which Spring wraps, and an infinite number, `INF`
(`6c136a93b7`, `5422cb2a82`), and for a bean unwrapped without a prefix, which
failed with a 500, an empty bean a property leaves out, and a property that
names the properties of the bean it holds; a response writer that fa
ils on a raw member threw its `IOException` (`9fab42e5ef`, `1a03cec0fc`,
`6683abfcad`). It now also marks a bean whose only output is an any-getter, a
bean written in a default serialization view of the JSON mapper, and two beans
of one class a bean unwraps, which failed with a type definition error
(`5d458bb9ff`). An `Accept` header of more than the 50 types Spring sorts
failed the negotiation of a problem with a 500; the problem is written in JSON
(`93aba35531`). A client that accepts no format a renderer writes the problem
in gets `application/problem+json`, where it got the action's view with a 200,
also when `respond` names a view, whose JSON view, such as `conflict.json.gsp`,
renders it where there is one, as for a client asking for JSON (`9f90d4f677`,
`914cb6cf64`, `afb4511946`, `7355c67257`). A browser `respond` answered with a
problem whose HTML view of the name does not exist got a missing-view error;
the problem is written as JSON then too (`70b395aa81`). That check asked
every view resolver, so a JSON or markup view counted as the HTML page and
was sent as `text/html`, and it also ran for the markup views renderer, so an
XML client got problem JSON; the HTML page is now looked up as the GSP view
resolver finds it, and a renderer that says itself whether it renders a value
is left alone (`fa25f2e13d`). A renderer an application registers for problems,
or for a type they share such as `Serializable`, renders them, with the
problem's status and `instance`, while one registered for `Object` stands in
for its format's default renderer (`460be00d42`, `e216843e4f`, `8e60d451b8`,
`d1c322aa46`), and a subclass of `XmlRenderer` registered for problems that
overrides `renderXml` renders them, as a `JsonRenderer` subclass does
(`1a07ac6e86`). A JSON or XML GSP `respond` names renders a problem too
(`228b4dc86b`), also where an application registers an HTML renderer for
problems, which rendered its own page in its place (`2f3cef0d84`), with its
model named with
the domain suffix, as for the HTML view (`7ed07d0fcf`), and a JSON view
rendering one in the place of problem JSON is sent as JSON (`e9d3d07aa8`). An
HTML page named with its `.gsp` suffix was not found, and a client asking for a
problem in XML from an action whose view of the name has no page got problem
JSON; it is written as without a view (`577a3647c4`, `f690d6753c`). The problem
XML Grails writes kept only the last of two members of one name, such as an
extension member named `status`, and moved an extension member named like a
property of a subclass into the namespace, where Spring's converter writes
both; it now writes the members in the order the mapper writes them, the
entries of an any-getter marked, and no longer reads them back with the JSON
mapper's read features, which wrote a list read as a Java array as its
`toString` (`b8bdcd2688`). Two beans of one class a bean unwraps with different
`@JsonIgnoreProperties` failed with a type definition error, and the elements
of
a list a property unwraps lost their marks, so their lists were written
unwrapped (`f22c8f5640`); a bean written as an array whose properties another
module orders was written as a list, its mark included (`543f1dc114`). The
guide says that the JSON mapper's settings, a default view among them, decide
the members of that XML, as of the JSON (`7986a5a490`). A problem whose view
has no page was then written as JSON or XML at once, passing over a renderer
the application registers for problems in a type the client accepts after HTML;
it is negotiated again, as without a view (`284a249fb7`). A client whose
`Accept` header Spring MVC cannot read, such as one with a quality value above
1 or more than 50 types, got problem JSON, where Spring MVC sends the problem's
status alone, without a body, and `406` for a status that is not an error
status; `respond` answers so too (`f500a4a045`). The problem XML marked the
entries of every any-getter, so those of a bean a subclass unwraps went into t
he namespace of RFC 9457, which my change above had brought in (`7020c0b801`);
it took its mapper from the first Jackson JSON converter, also one that does
not write problems (`4fc899fdda`); and it wrote a problem nested deeper than
the mapper writes, overflowing the stack where it was thousands deep, where
Spring's converters fail; it fails as they do, at the mapper's limit
(`6b66f25e10`). With a second `RendererRegistry` bean, an action an XML view
renders failed with a 500 (`ea8d7e3e7d`). The type of a problem was decided by
parsing the `Accept` header, which matches Spring MVC only under Spring Boot's
default negotiation; `respond` now asks the application's content negotiation
manager, so `favor-parameter`, a default content type and a
`WebMvcConfigurer`'s strategies decide as for a Spring MVC controller, and a
request that negotiation rejects gets the status alone where the problem is
written as without a view, which the guides now say rather than claiming it
always does (`5b0
1028cdf`, `dd33ff0184`). The problem XML wrote a primitive array one level
past the mapper's limit, and took the JSON converter's own mapper where Spring
writes `application/problem+json` with one the application registers for the
problem's type (`5aa17b6e63`, `a0d80144ab`). It asked Spring's converters
whether they write a `ProblemDetail` rather than the problem's own class, so a
converter that writes only an application's subclass, such as a JAXB one, was
passed over where Spring MVC writes the problem with it; it wrote the members
of RFC 9457 in no namespace where the mapper's naming strategy renames them;
and it wrote a control character XML cannot hold as it is, which is not
well-formed XML, where Spring's XML converter fails; it fails with the same
message (`365bcfb4d2`, `699918555c`, `48006806f9`). It still took the names of
those members from a plain problem, so a subclass that names them otherwise,
with a naming strategy of its own or a getter it overrides, wrote them in no
namespace, and a property a module adds to every problem in that of RFC 9457,
and it wrote the members of a problem an extension member holds in none; it now
tells them by their getters, as Spring's mix-in does (`103b9a01b6`). It wrote a
carriage return as it is, which a parser reads as a line feed, and half of a
surrogate pair as `?`, and it failed on U+FFFE and U+FFFF, where Spring's
converter writes the three as character references and fails on half a pair; it
now writes and fails as that converter does, with its messages (`b400a21839`).
- `respond problem, status: 400` was answered with the problem's status. The
argument now decides, and a problem that carries another keeps its own with a
warning, as a `ResponseEntity` does in Spring MVC (`214d550e55`). A status
`HttpStatus` has no constant for, such as 499, failed (`f647b2be3d`). `respond
problem, jsonConfiguration: 'catalog'` failed when the legacy converter wrote
the problem (`9104b2a475`), and an unregistered name went unnoticed for a
problem (`f40ad252fd`), and where a JSON GSP view rendered the response
(`cecc5f9b6b`). `respond errors, jsonConfiguration: 'catalog'` was written by
the configuration's mapper as `application/json` without the fields of a
problem, and the errors of a value ignored the name; validation errors are
written as without a configuration, and a wrong name throws for both
(`e411651fee`). `includes` and `excludes` reshaped a problem, `{"status":409}`
for `excludes: ['title', 'detail']`; a problem is now written whole
(`c6246b0950`). `ren
der json:` with an empty `jsonConfiguration`, such as an empty request
parameter, failed where `respond` writes with the default (`4c5cfa7f1d`), and a
registry created with a `null` mapper failed only on its first write
(`f7f2b6bf50`).
- `respond` answered a client that lists a format the action does not
respond in ahead of one it does in the next, but labelled it with the first, so
with `json.spring` the legacy converter wrote JSON labelled `application/xml`
(`f3fb6121b8`). A client that accepts any type is answered in the first of the
action's formats a renderer renders the value in, where the first listed was
taken whatever rendered it, so without `grails-xml` an action whose formats
list XML first answered `curl` and browsers with 406; validation errors try the
client's next type too, where they stopped at its first with 404; and a markup
view renderer with no XML renderer to fall back to is passed over for a value
no markup view renders (`edf2883d50`). A client that accepts only a problem
type is answered as one that asks for that format (`3f2e164432`). A renderer
that renders some values only, such as that of markup views, was asked whether
it renders the value in the client's first type rather than the ty
pe it would render it in (`0f7c8f809b`). A controller Spring did not create,
such as one a test creates with `new`, responded with a bare registry, which no
longer renders XML, and answered an XML client with 406; it uses the
application's, by name where there are several (`79ae9ed511`, `3ede67c589`). A
Jackson 2 converter, which writes UTF-8 for any encoding Jackson has no writer
of, had its output decoded in the response's encoding (`02221e9f14`).
- A serialization failure part-way through a body a Spring converter writes
committed the response, so the error page could not apply (`f9aac87221`,
`4922ff54df`). That holds while the body fits in the response buffer, for JSON
and XML (`6bbaf01a35`, `4e5993e7c5`). A named JSON configuration's write, for
`render json:` and `respond` alike, closed the response writer when it failed,
sending a truncated 200 (`94fed04f54`). It also reported an I/O failure of the
value being written, such as a getter reading a missing file, as a failure of
the response writer, without Jackson's path (`c45aa04a1c`), and a value that
failed while the writer was broken as the writer's failure (`826c588d87`). A
response `respond` writes with a named configuration threw Jackson's own
exception, as did an XML problem written without `jackson-dataformat-xml`, and
a response writer that failed on the converter's prefix threw its
`IOException`; they now fail as Spring MVC's converters fail (`6e4bacdef2`).
- In `respond`'s Spring JSON path, a value a serializer wrote with
`JsonGenerator.writePOJO` was written by the application's mapper, a domain
class instance as a plain bean with GORM's own properties (`550d456899`). With
`spring.jackson.serialization.wrap-root-value`, the response was malformed,
`{"GrailsJsonValue"{…}}`: the Grails mapper now writes the whole response, the
wrapper of the value included, after the converter's prefix (`e6a6fc943c`). It
did so in two writes, with two serialization contexts, so an object a
serializer wrote with `writePOJO` was written again where it repeated one being
written; one write writes the value (`f20adebe98`).
- With JSON views, `respond problemDetail` rendered the action's view, class
template or `_object.gson` as 200 `application/json`. Views found by convention
now skip a problem (`ba8d2bfff0`). A view `respond` names still renders it,
with the problem's status (`0810d8f82b`, `7d25dce6b2`). A view renderer with
nothing to fall back to sends its 406 as `respond` sends its own (`ba2edffe22`,
`7d38831f31`). Validation errors with no errors template were rendered with the
object template, which forge's JSON views feature and the REST API profile
generate, as `null` with a 200; they are written as without JSON views, with a
422, which the guide's advice to delete `_errors.gson` now gets (`631f9c0b2d`).
`grails-openapi` still described those errors as a 200 rendered with
`_object.gson` (`3757578c7d`), and described the JSON 422 with the legacy
errors object whatever the settings; it describes the
`application/problem+json` problem `json.spring` writes and the errors `legacy:
false` writes
(`1da5c6fa20`), and they were written in UTF-8 whatever
`grails.converters.encoding` says (`8ee1130b4d`). It required the problem's
`type`, which Spring leaves out where it is `about:blank`, and read `legacy`
with a default of its own; it describes the problem as Spring writes it and
reads the setting as the converter does (`cde9474ee4`), also where a generator
is created without settings (`1047523cd3`).
- A controller's `validationProblemDetailFactory` property, autowired by
name, got the plugin's factory rather than the application's (`4ed91133e5`). An
application `@Bean` of that name was dropped, because plugin beans are
registered before configuration classes are read (`2e2fcd6bc7`).
- A `JsonRenderer` bean's own includes and excludes were dropped when the
action named a `jsonConfiguration` (`33f28ad87e`).
`addDefaultRenderer`/`addContainerRenderer` skipped the registry's JSON
settings (`6f1164594d`). The warning that the legacy converter writes a problem
and the legacy `respond` warning shared one flag, so the first silenced the
other (`3fd01a6781`). The warning that a projection was not applied was logged
for every response; it is logged once for each type (`bfad5f454b`).
- An empty `includes` list wrote every property on `respond`'s Spring path,
with a named configuration, with `render json:`, under `legacy: false` and on
the Spring XML path, where Grails 8 wrote none; it now includes none on all of
them (`9ff2ce26da`). `render json:` wrote UTF-8 whatever
`grails.converters.encoding` said, where `respond` and the converter follow it,
also with a `contentType` argument (`78d8a93c8b`, `3fbb51697b`).
- Which XML errors renderer answered depended on bean order. The plugin's
renderer now holds the XML media types over an application's renderer beans, as
Grails 8's registry did, and a renderer added after initialization still
overrides it (`7924312ae8`, `c2923fb21e`, replacing `f286a5281c`). The plugin's
renderer beans are now `grailsXmlRenderer` and `grailsXmlErrorsRenderer`, so an
application's `xmlRenderer` bean no longer replaces them (`3f185196a0`). A
plugin's fallback renderer for a type the registry renders itself, such as
JSON, was replaced by the registry's own default; it replaces it, as a fallback
container renderer does (`536498e59d`).
Elsewhere:
- `XMLCodec` had moved into `grails-xml`, so `encodeAsXML` stopped escaping
without it. It is back in `grails-converters`, and renders objects as XML only
when `grails-xml` is present (`5fbd5de02a`), which a native image now keeps
through the runtime hints `grails-xml` registers (`f5e491f8ee`). Without
`grails-xml`, `request.XML` read a request attribute of that name, `null`,
where the guide said it fails; it throws an `IllegalStateException` that names
the module (`7e88a3721f`). A library compiled with `@CompileStatic` against
Grails 8 calls `ConvertersExtension.getXML`, which had moved with XML, and
failed with `NoSuchMethodError`; the class keeps the Grails 8 methods,
deprecated, and is no longer an extension class (`14889230d9`).
`ConvertersConfigurationInitializer.initialize()` configures the XML converter
again where `grails-xml` is present, as in Grails 8, so a test that initializes
the converters itself writes XML, while an application still configures it once
(`d64534d466
`).
- Web unit tests registered the converters and XML beans before
`doWithConfig` reached the environment, so `legacy: false` and
`domain.jackson.enabled` were ignored there (`9464beac9d`). A unit test request
matched its content type by exact name, where an application matches it in any
case and with parameters (`8539ecea82`). The renderer registry now comes from
the REST plugin when the test's context is first refreshed, before the test
defines its GSP locator, so `respond` rendered no `.json.gsp` view in a
controller unit test; the JSON renderer finds the locator when it writes, as
the XML one does (`17175a50ac`). A unit test request kept the format of the
content type it was first given (`943c3a276e`). The web test traits keep the
URL mappings holder, link generator and locale resolver a spec defines, and
with it the locale resolver `grails.i18n.localeResolver` configures; specs now
cover these and Spring Boot's converter beans, and the guide says which beans a
test keeps (`4c9db
da9e6`). The URL mappings plugin's link generator and holder, which a spec
including the controllers plugin loads, were kept too, so absolute links came
from the request and the holder never saw the application's mappings; the test
application builder now marks the beans a test declares, and only those are
kept (`b27d55d5ff`). A holder or link generator a test declares under another
name, with the name as an alias, was replaced; it is kept, and the guide says
that with the URL mappings plugin a test declares its holder in `doWithSpring`
or `beanRegistrar()`, that a bean of an imported configuration class is
replaced, and that `beanRegistrar()` is too late for a mapper bean, as
`doWithSpring` is (`ebe25a74a5`, `78bb7c95e3`, `db2c91a402`). A bean of a
`@Bean` method a test's configuration class inherits did not count as one the
test declares (`a5570eac03`), and `JsonViewUnitTest` replaced the beans a spec
declares and the locale resolver the application configures; it keeps them by th
e same rules (`0975a9bf30`), also from a static `@Bean` method a configuration
class inherits (`7374e61aa7`). The guide says that a web test keeps any locale
resolver its context has (`af91ab91b9`). A component class nested in a test's
configuration class also counted as a bean the test declares, and replaced a
stand-in of the harness such as `messageSource`; only the beans of `@Bean`
methods count (`80d002314d`). A web unit test had no content negotiation
manager, so `respond` chose the type of a problem from the `Accept` header
alone; the traits build Spring MVC's, from the test's converters,
`spring.mvc.contentnegotiation.*` and the test's `WebMvcConfigurer` beans,
unless the test declares one (`db1e943fb0`).
- The applications forge and the profiles generate list `grails-web-boot`
and the other Grails modules rather than the web starter, so they lost XML on
upgrade. `grails-web-boot` now brings `grails-xml` through Grails 9, as the
starter does (`e02e58b737`), and with it the REST, controllers, data source,
converters and web data binding modules, which section 3 now names
(`66969d4a01`). The shell CLI's distribution names `grails-xml` among its
projects, which reached it only through `grails-web-boot` (`f9e626ea83`).
- A renderer, which is a `MimeTypeProvider`, that depended on a Spring MVC
bean failed startup with a cycle through the content negotiation manager; the
formats Grails registers with Spring MVC now come from `grails.mime.types`
(`edd725c169`), and the configurer constructor that took them from a strategy
built on first use, which brought the cycle back, is gone (`c4163f93b1`). The
response's MIME lookup is compiled statically, so that a native image needs no
reflection for it (`763b9d61aa`).
- `request.format` threw in an application context without a `MimeUtility`
bean (`e6905b117d`). The legacy XML warning names `grails-web-boot` as well as
the starter (`01156cf593`). The notice that `grails.converters.json.legacy` is
not set was not logged again after a restart in the same JVM, as the other
notices are (`01065a4753`).
- The `Accept` parser sorted the types before promoting a `+xml` type over
`application/xml`, which Grails 8 did in header order, and matched `*;q=0.2`,
which Java clients send, as `*/*` (`2e74015bc6`, `5a4d77cae8`). `Accept:
text/xml, application/xml;q=0.1, application/json;q=0.5` negotiated JSON, since
the quality merged for the two XML types was never read, as in Grails 8, which
a client asking for `application/problem+xml` now reaches more often, and the
merged type takes the place of the one the header lists first (`1c8c0c5463`,
`e38be68cb0`). A quality written `Q=0.1` was ignored, since parameter names
kept the case the client sent; they are read ignoring case, as Spring reads
them (`b7fd11c999`). A `Content-Type` that no configured type matches kept its
case, so a JSON API body sent as `application/VND.API+json` was not bound; its
type is read in lower case (`08185aed1e`), and a binding source creator
registered for a type with capitals is found for it, since media type nam
es are compared ignoring case (`ed9398ae52`). A problem type was read as the
first configured type of its format, `text/xml` by default, so
`application/problem+xml, application/atom+xml` resolved to `xml` or `atom`
depending on the order of `grails.mime.types`; it is read as `application/xml`
or `application/json` where those are configured (`2ee0ca4a18`).
- `grails.mime.disable.accept.header.userAgents` matched its first entry
with its case, and an entry that is not a regular expression failed every
request. Every entry is now a regular expression of its own, matched ignoring
case anywhere in the `User-Agent` header, where Grails 8 joined them into one,
in which an entry's inline flags applied to the next, a back reference was
renumbered and a blank entry matched every client; a comma-separated string is
read as a list, and one that is not a regular expression is named as the
application starts, while a `Pattern` is used as it is, also in a list
(`96ee149746`, `53435d5c54`, `5a156b3d1f`, `cf6a6aa7c8`).
- A `HalJsonRenderer` registers a binding source creator of its own for its
type, which `grails.databinding.json.jackson` never reached, so its HAL bodies
were parsed by `JsonSlurper`; it now has the application's HAL creator parse
them where `grails.databinding.json.jackson` is on, and otherwise its own, with
a default `JsonSlurper` as in Grails 8, which a LAX `JsonSlurper` bean of the
application had reached (`8556accfa3`, `2f7c4135d9`). With the setting, the
renderer took the application's creator only where it was a
`HalJsonDataBindingSourceCreator`, and the JSON bodies of a type no creator
bean handles, where an application replaces `jsonDataBindingSourceCreator` with
a creator for one class, were parsed by an unwired creator with a default
`JsonSlurper`; both follow the setting now (`1b102a921a`). With `grails-xml`,
the XML bodies of a type no creator bean handles were ignored, since the
registry's XML defaults had not moved with XML; they are parsed as in Grails 8
(`44ff746
28b`). The JSON creator the setting creates by its class has its constructor,
fields and methods registered for an ahead-of-time image (`c4c8ae5073`,
`8412a4be61`). With the setting, a HAL renderer handed its bodies to the
application's HAL creator whatever class it handles, and a
`DefaultDataBindingSourceRegistry` an application defines itself parsed with
`JsonSlurper`; both follow the setting now (`2bcbbbe991`, `4ec951ed93`). A
`HalJsonRenderer` set to parse with Jackson without the setting parsed with
`JsonSlurper` where the application's creator handles another class; it creates
its own Jackson creator (`0661efe9a1`), and where Grails' HAL creator, which
without the setting parses with `JsonSlurper`, handles the type, it no longer
hands its bodies to it (`7df46e4aa2`). A creator registered for an interface
was never found, since the registry searched the interfaces of
`java.lang.Class` (`2c1030f669`). With Jackson binding,
`spring.jackson.deserialization.unwrap-root-value` faile
d every JSON, HAL and JSON API body; a body is read without a root name
(`4fcc1e50de`). The public `initialize()` the creators logged their notice from
was called in the place of a subclass's own `initialize()`; the method is
private (`1517e0d865`). The binding reader also took the mapper's features that
change how an untyped value is read, so arrays came as `Object[]`, which the
JSON API creator dropped, and whole numbers as `Long` or `BigInteger`, and its
default typing, so a body without type ids failed and one could name a class
for Grails to create; a body is read without them (`fa1bc97777`, `fc8fdb15b6`).
The copy that turned default typing off was made of every mapper and is always
a plain `JsonMapper`, so an application's own mapper class read bodies without
its overrides; only a mapper with default typing is copied (`5220de6ff6`).
Design: one gated property writer replaces three near-identical ones
(`d199062b9f`). The MIME fallback no longer keeps static state (`a1f6e39184`).
The two converter initializers share a base class, and
`SETTING_CONVERTERS_XML_DEEP` is back as a deprecated constant (`ffcc408838`).
`grails.converters.XML`, `XmlRenderer`, `XmlCollectionRenderer` and the Atom
renderers are now `forRemoval` too, as the guide says, and HAL XML, Atom and
Vnd.Error XML warn once (`916ef6e8b9`). The legacy JSON hooks are `forRemoval`
(`2826669ac2`). The default of `grails.converters.json.legacy` is private
again, read through `ConvertersConfigurationInitializer.isLegacyJson`, so code
compiled against Grails 9 does not keep it (`0c50a1cdb2`), and the JSON binding
source creator keeps its plumbing private (`7d7fd884a7`). The path of each
object being written is kept only where `PATH` may write it (`9afaa645ca`),
which is now decided once for each write, where it read the setting for each
bean, also for the
objects reached again (`25f026dbfb`, `a00a27cd4a`), the serializer of an
associated instance with a serializer of its own is found once for each class
(`8d77fd882d`), and the legacy converter renders without copies of its mapper
support it does not read (`2ccca00f77`). `PathCapturingJSONWriterWrapper`
answers the nesting depth of the writer it wraps (`d7fcc009d7`), and
`grails-xml` is among the packages listed for GitHub's dependency graph
(`94d80588f1`). `JSONWriter(Writer, boolean)` pretty prints with a default
mapper, which the guide names in place of the internal `JsonMapperSupport`
(`46b1d76107`), and the test fixtures `grails-converters` shares with
`grails-xml`'s specs are neither published nor in the API docs (`f3e64d47e3`).
The data binding plugin says why it sets the registry's Jackson default itself
(`2445b9d937`), and the Javadoc of the problem XML's marks describes them as
written (`267102ca70`), as do those of the legacy writer's default mapper and
of the mapper the JS
ON converter writes through (`7656a09d57`, `f5caa41e02`). The problem XML
derives its mapper once for each JSON mapper, where it kept one and derived it
again whenever a converter's mapper for one problem type took the place of
another's (`49505489fb`, `e2d71c291c`), and it takes that mapper with the one
lookup the converter makes, where it also walked the problem's supertypes,
which never changed the result (`a30f1d48bc`).
Tests: specs that could not fail now can, more assertions are exact, the
JSON halves of the specs moved to `grails-xml` are back in their modules, the
proxy fixtures implement Hibernate's proxy interface as Hibernate's proxies do,
`XmlGrailsPluginSpec` no longer leaves the XML converter configured for later
specs, and two specs no longer leave a request bound to the thread
(`cfc442fb3c`, `2d25a9d811`..`540b191b58`, `dc40154b2b`,
`ae048363a7`..`2435d33e2e`). Beyond the specs of each fix above, new ones cover
the number types Jackson binds, the default `respond` errors body, the XHR
`Accept` rules, domain `@JsonPropertyOrder` and `@JsonFilter`, the formats
Grails registers with Spring MVC, Spring Boot's converter customizers in a
controller unit test, the legacy parsing notice, positive controls for the
provider aliases, and the registry default the data binding plugin sets itself,
which no spec failed without (`3aa15e5590`, `168e3f338f`, `4edeed4dc5`,
`64c56ccf71`..`60a9a6ec84`, `3
7813f114c`..`ecc5b30c4b`, `0a6bbd08d3`, `778ce44bb5`, `8af5caefbf`). Three
specs checked `json`, which Spring registers itself, where they meant the `hal`
alias only Grails registers (`8883541edb`), and the `@Resource` update and
patch features, which run in an isolated task, relied on a test request keeping
the format it first resolved; they expect the redirect a form request gets
(`b573005163`). The latest pin the guide's problem-type examples to the default
MIME types, the class of a problem the JSON converter is asked about, the depth
check of the arrays a delegate writes whole, and what the second-registry XML
view spec checks (`4fa3a410b6`, `49fff5e6dd`, `a0829e9db5`, `3a814ce0eb`), and
then that a problem is written in XML only where the action responds in XML,
and with the mapper the JSON converter selects for it where mappers are
registered for a problem's class and its superclass (`d351422877`,
`b9480c6aff`).
Since ten rounds of review kept finding one case at a time, three matrix
specs in `grails-test-suite-web` now write every Jackson feature, cycle shape
and deep association these changes meet through every way of writing JSON:
`respond`, `render json:`, the registry and named configurations (plain, with a
view and with serializers of their own), the JSON converter, and `respond`
through the converter. They write each with every `includes`/`excludes` option
and circular reference behaviour, and compare it with what Spring Boot's own
mapper writes, or with what Grails 8 wrote where it could, which a legacy copy
of each spec proves (2,748 rows; `d4758dded4`, `14b2d112a1`, `90683781ca`,
`01ba281d0b`). The fixes from `1837af0ba3` to `d7fcc009d7` are what they, and a
review of those fixes alone, found; the documented differences, such as the
converter's enums by name, are part of what they expect. Nine full reviews of
the areas the matrix does not cover, MIME and `Accept`, `respond`, XML
, problems and binding, found the fixes from `8883541edb` on.
Docs:
- Section 4 lists the five settings with their defaults. The
`render`/`respond` reference pages cover `json:`, `jsonConfiguration:` and
`ProblemDetail`. Stale passages on the converter, Vnd.Error, OpenAPI and
`@Resource` pages are corrected (`9374e756a7`, `3be76aa5b5`).
- With JSON views, `_errors.gson` renders validation errors. The 406 and 404
are sent as errors. A factory bean named `validationProblemDetailFactory` needs
bean definition overriding (`c4ae2b459f`).
- The docs named `spring.jackson.parser.*`, which Boot 4 removed
(`5c5e91dc56`).
- The docs were checked against the code in every round. The REST guide has
a page on customizing JSON with Jackson and on named configurations, `view` and
`attribute` included, in place of marshaller pages the table of contents never
listed (`4954e3edee`), and What's New has an entry (`9442aa2ab6`). Corrections
cover which mapper `render json:`, `respond` and the converter write with, and
which of Spring Boot's serializers apply on each path; what `includes`,
`excludes`, `deep` and named configurations apply to; what of the mapper
applies to binding, the binding source creator interface with an example that
compiles, and what a body binds without `grails-xml`; the order of the `Accept`
types `withFormat` and `respond` follow, how a problem type and a `+xml` type
resolve, and the MIME methods Grails 9 removes or changes; problems, their
views, statuses and XML; where `grails-xml` comes from; metadata for the
converter, rendering and MIME settings; and Javadoc that said less or oth
er than the code does (`beaeeb8efd`..`3c79858d48`, `39dcf12740`..`825a08e3c7`,
`2d6dcc16ac`..`8fb967f1d4`, `90138f0442`, `a2c3cd40d8`, and the docs commits
since). The latest say which header Grails 8 resolved to `all`, that `respond`
writes a problem for a client that accepts only a problem type, that a creator
added to the registry for a problem type is passed over, that in the
`withFormat` example the order of the header decides, not that of the block,
that Grails reads the format of a rejected request from the request, and in
which sections the converter's additions are described (`d9221ca736`,
`b53462439d`, `ff64e7bb70`, `987a06b506`, `da405896fb`, `a7d8ad45c4`); then
that a problem for a client of a format the action does not respond in is
written as JSON, which problem types are read as `application/json` and
`application/xml`, what the converter's mapper is given and which writes enums
by name, and how a web unit test reads the `format` parameter, applies the
negotiation pro
perties, serves an application that declares `@EnableWebMvc`, and replaces a
stand-in such as `messageSource` (`ea8097254e`, `8511f4426d`, `3f4d64c184`,
`e240de8352`, `dc9a4a72bd`, `02f59eaaaf`).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]