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]

Reply via email to