This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 8a1d669143 feat(jsonschema): add typed JsonSchema bean generation
bridge and TODO-8 docs/tests
8a1d669143 is described below
commit 8a1d6691435823032210fb75febdd8c0c7f3f0c6
Author: James Bognar <[email protected]>
AuthorDate: Fri May 15 13:34:21 2026 -0400
feat(jsonschema): add typed JsonSchema bean generation bridge and TODO-8
docs/tests
---
.../apache/juneau/bean/jsonschema/JsonSchema.java | 93 +++++++++
.../bean/jsonschema/JsonSchemaBeanGenerator.java | 212 +++++++++++++++++++++
.../juneau/bean/jsonschema/JsonSchemaProperty.java | 18 ++
.../juneau/bean/jsonschema/JsonSchemaRef.java | 18 ++
.../juneau/bean/jsonschema/package-info.java | 10 +
.../jsonschema/JsonSchemaBeanGenerator_Test.java | 192 +++++++++++++++++++
.../juneau/bean/jsonschema/JsonSchema_Test.java | 21 ++
...13-system-properties-to-settings-conversion.md} | 2 +-
...FINISHED-21-bean-annotations-inject-package.md} | 0
...md => FINISHED-8-jsonschema-bean-generation.md} | 8 +
todo/TODO.md | 4 -
11 files changed, 573 insertions(+), 5 deletions(-)
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
index 851d86cc76..2d8ca6a927 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
@@ -22,6 +22,7 @@ import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
+import java.lang.reflect.*;
import java.net.*;
import java.util.*;
@@ -103,6 +104,7 @@ import org.apache.juneau.swap.*;
* <li><b>Fluent API:</b> All setter methods return <c>this</c> for method
chaining
* <li><b>Type Safety:</b> Uses enums and typed collections for validation
* <li><b>Serialization:</b> Can be serialized to any format supported by
Juneau (JSON, XML, HTML, etc.)
+ * <li><b>Auto Generation:</b> Use {@link JsonSchemaBeanGenerator} to
generate schemas from Java types
* </ul>
*
* <h5 class='section'>Common Use Cases:</h5>
@@ -362,6 +364,9 @@ public class JsonSchema {
private JsonSchema else_; // Draft 07+
private Boolean readOnly; // Draft 07+
private Boolean writeOnly; // Draft 07+
+ private String format; //
Annotation-level keyword
+ private String comment; // Draft 07+,
serialized as $comment
+ private Boolean deprecated; // Draft 2019-09+
private String contentMediaType; // Draft 07+
private String contentEncoding; // Draft 07+
@@ -378,6 +383,26 @@ public class JsonSchema {
*/
public JsonSchema() { /* Empty constructor. */ }
+ /**
+ * Generates a {@link JsonSchema} bean from the specified type using
{@link JsonSchemaBeanGenerator#DEFAULT}.
+ *
+ * @param type The type to generate a schema for.
+ * @return The generated schema bean.
+ */
+ public static JsonSchema of(Type type) {
+ return JsonSchemaBeanGenerator.DEFAULT.generate(type);
+ }
+
+ /**
+ * Generates a {@link JsonSchema} bean from the specified class using
{@link JsonSchemaBeanGenerator#DEFAULT}.
+ *
+ * @param type The class to generate a schema for.
+ * @return The generated schema bean.
+ */
+ public static JsonSchema of(Class<?> type) {
+ return JsonSchemaBeanGenerator.DEFAULT.generate(type);
+ }
+
/**
* Bean property appender: <property>additionalItems</property>.
*
@@ -785,6 +810,27 @@ public class JsonSchema {
*/
public String getContentMediaType() { return contentMediaType; }
+ /**
+ * Bean property getter: <property>deprecated</property>.
+ *
+ * <p>
+ * This property was added in Draft 2019-09.
+ *
+ * @return The value of the <property>deprecated</property> property on
this bean, or <jk>null</jk> if it is not set.
+ */
+ public Boolean getDeprecated() { return deprecated; }
+
+ /**
+ * Bean property getter: <property>$comment</property>.
+ *
+ * <p>
+ * This property was added in Draft 07.
+ *
+ * @return The value of the <property>$comment</property> property on
this bean, or <jk>null</jk> if it is not set.
+ */
+ @BeanProp("$comment")
+ public String getComment() { return comment; }
+
/**
* Bean property getter: <property>definitions</property>.
*
@@ -821,6 +867,13 @@ public class JsonSchema {
*/
public Map<String,JsonSchema> getDependencies() { return dependencies; }
+ /**
+ * Bean property getter: <property>format</property>.
+ *
+ * @return The value of the <property>format</property> property on
this bean, or <jk>null</jk> if it is not set.
+ */
+ public String getFormat() { return format; }
+
/**
* Bean property getter: <property>dependentRequired</property>.
*
@@ -1402,6 +1455,35 @@ public class JsonSchema {
return this;
}
+ /**
+ * Bean property setter: <property>deprecated</property>.
+ *
+ * <p>
+ * This property was added in Draft 2019-09.
+ *
+ * @param value The new value for the <property>deprecated</property>
property on this bean.
+ * @return This object.
+ */
+ public JsonSchema setDeprecated(Boolean value) {
+ this.deprecated = value;
+ return this;
+ }
+
+ /**
+ * Bean property setter: <property>$comment</property>.
+ *
+ * <p>
+ * This property was added in Draft 07.
+ *
+ * @param value The new value for the <property>$comment</property>
property on this bean.
+ * @return This object.
+ */
+ @BeanProp("$comment")
+ public JsonSchema setComment(String value) {
+ this.comment = value;
+ return this;
+ }
+
/**
* Bean property setter: <property>definitions</property>.
*
@@ -1446,6 +1528,17 @@ public class JsonSchema {
return this;
}
+ /**
+ * Bean property setter: <property>format</property>.
+ *
+ * @param value The new value for the <property>format</property>
property on this bean.
+ * @return This object.
+ */
+ public JsonSchema setFormat(String value) {
+ this.format = value;
+ return this;
+ }
+
/**
* Bean property setter: <property>dependentRequired</property>.
*
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator.java
new file mode 100644
index 0000000000..89eebfbde4
--- /dev/null
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.bean.jsonschema;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
+
+import java.lang.reflect.*;
+
+import org.apache.juneau.collections.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.jsonschema.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * Bridge for generating typed {@link JsonSchema} beans from Java types.
+ *
+ * <p>
+ * {@link JsonSchemaGenerator} in <c>juneau-marshall</c> produces
<c>JsonMap</c> output. This class wraps that
+ * generator and converts the generated map into typed <c>JsonSchema</c> beans
in <c>juneau-bean-jsonschema</c>.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Default generation.</jc>
+ * JsonSchema <jv>s1</jv> =
JsonSchemaBeanGenerator.<jsf>DEFAULT</jsf>.generate(MyBean.<jk>class</jk>);
+ *
+ * <jc>// Custom generation with bean defs and descriptions.</jc>
+ * JsonSchema <jv>s2</jv> = JsonSchemaBeanGenerator.<jsm>create</jsm>()
+ * .useBeanDefs()
+ * .addDescriptionsTo(TypeCategory.<jsf>ANY</jsf>)
+ * .build()
+ * .generate(MyBean.<jk>class</jk>);
+ * </p>
+ */
+public final class JsonSchemaBeanGenerator {
+
+ /** Reusable default instance. */
+ public static final JsonSchemaBeanGenerator DEFAULT = create().build();
+
+ /** Creates a new builder. */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ private final JsonSchemaGenerator generator;
+
+ private JsonSchemaBeanGenerator(Builder builder) {
+ this.generator = builder.generatorBuilder.build();
+ }
+
+ /**
+ * Generates a schema bean from a Java type.
+ *
+ * @param type The Java type.
+ * @return The generated schema bean.
+ */
+ public JsonSchema generate(Type type) {
+ assertArgNotNull("type", type);
+ try {
+ var session = generator.getSession();
+ var root = session.getSchema(type);
+ if (root == null)
+ return null;
+ var defs = session.getBeanDefs();
+ if (defs != null && ! defs.isEmpty())
+ root.append("$defs", defs);
+ return toBean(root);
+ } catch (Exception e) {
+ throw toRex(e);
+ }
+ }
+
+ /**
+ * Generates a schema bean from a Java class.
+ *
+ * @param type The Java class.
+ * @return The generated schema bean.
+ */
+ public JsonSchema generate(Class<?> type) {
+ return generate((Type)type);
+ }
+
+ /**
+ * Generates a schema bean from an object.
+ *
+ * <p>
+ * The value can be a POJO or a <c>Class</c>/<c>Type</c>.
+ *
+ * @param o The value to infer a schema from.
+ * @return The generated schema bean.
+ */
+ public JsonSchema generate(Object o) {
+ assertArgNotNull("o", o);
+ try {
+ var session = generator.getSession();
+ var root = session.getSchema(o);
+ if (root == null)
+ return null;
+ var defs = session.getBeanDefs();
+ if (defs != null && ! defs.isEmpty())
+ root.append("$defs", defs);
+ return toBean(root);
+ } catch (Exception e) {
+ throw toRex(e);
+ }
+ }
+
+ /**
+ * Converts a generated schema map into a typed bean.
+ *
+ * <p>
+ * Conversion uses a JSON roundtrip to ensure the swaps declared on
{@link JsonSchema} are honored.
+ *
+ * @param schemaMap The generated schema map.
+ * @return The typed schema bean.
+ */
+ public static JsonSchema toBean(JsonMap schemaMap) {
+ assertArgNotNull("schemaMap", schemaMap);
+ try {
+ var json = JsonSerializer.DEFAULT.toString(schemaMap);
+ return
JsonParser.create().ignoreUnknownBeanProperties().build().parse(json,
JsonSchema.class);
+ } catch (ParseException e) {
+ throw toRex(e);
+ }
+ }
+
+ /** Builder for {@link JsonSchemaBeanGenerator}. */
+ public static final class Builder {
+ private final JsonSchemaGenerator.Builder generatorBuilder =
JsonSchemaGenerator.create();
+
+ private Builder() {}
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#addDescriptionsTo(TypeCategory...)}.
*/
+ public Builder addDescriptionsTo(TypeCategory...values) {
+ generatorBuilder.addDescriptionsTo(values);
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#addExamplesTo(TypeCategory...)}.
*/
+ public Builder addExamplesTo(TypeCategory...values) {
+ generatorBuilder.addExamplesTo(values);
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#allowNestedDescriptions()}.
*/
+ public Builder allowNestedDescriptions() {
+ generatorBuilder.allowNestedDescriptions();
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#allowNestedDescriptions(boolean)}.
*/
+ public Builder allowNestedDescriptions(boolean value) {
+ generatorBuilder.allowNestedDescriptions(value);
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#allowNestedExamples()}.
*/
+ public Builder allowNestedExamples() {
+ generatorBuilder.allowNestedExamples();
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#allowNestedExamples(boolean)}.
*/
+ public Builder allowNestedExamples(boolean value) {
+ generatorBuilder.allowNestedExamples(value);
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#beanDefMapper(Class)}.
*/
+ public Builder beanDefMapper(Class<? extends
MarshallingDefMapper> value) {
+ generatorBuilder.beanDefMapper(value);
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#ignoreTypes(String...)}.
*/
+ public Builder ignoreTypes(String...values) {
+ generatorBuilder.ignoreTypes(values);
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#useBeanDefs()}. */
+ public Builder useBeanDefs() {
+ generatorBuilder.useBeanDefs();
+ return this;
+ }
+
+ /** Mirrors {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator.Builder#useBeanDefs(boolean)}.
*/
+ public Builder useBeanDefs(boolean value) {
+ generatorBuilder.useBeanDefs(value);
+ return this;
+ }
+
+ /** Builds a new generator. */
+ public JsonSchemaBeanGenerator build() {
+ return new JsonSchemaBeanGenerator(this);
+ }
+ }
+}
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaProperty.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaProperty.java
index 14664ffd84..ee81d26120 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaProperty.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaProperty.java
@@ -192,6 +192,18 @@ public class JsonSchemaProperty extends JsonSchema {
return this;
}
+ @Override /* Overridden from JsonSchema */
+ public JsonSchemaProperty setDeprecated(Boolean value) {
+ super.setDeprecated(value);
+ return this;
+ }
+
+ @Override /* Overridden from JsonSchema */
+ public JsonSchemaProperty setComment(String value) {
+ super.setComment(value);
+ return this;
+ }
+
@Override /* Overridden from JsonSchema */
public JsonSchemaProperty setDefinitions(Map<String,JsonSchema> value) {
super.setDefinitions(value);
@@ -222,6 +234,12 @@ public class JsonSchemaProperty extends JsonSchema {
return this;
}
+ @Override /* Overridden from JsonSchema */
+ public JsonSchemaProperty setFormat(String value) {
+ super.setFormat(value);
+ return this;
+ }
+
@Override /* Overridden from JsonSchema */
public JsonSchemaProperty setEnum(List<Object> value) {
super.setEnum(value);
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaRef.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaRef.java
index abe0241f64..1b01676b44 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaRef.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaRef.java
@@ -193,6 +193,18 @@ public class JsonSchemaRef extends JsonSchema {
return this;
}
+ @Override /* Overridden from JsonSchema */
+ public JsonSchemaRef setDeprecated(Boolean value) {
+ super.setDeprecated(value);
+ return this;
+ }
+
+ @Override /* Overridden from JsonSchema */
+ public JsonSchemaRef setComment(String value) {
+ super.setComment(value);
+ return this;
+ }
+
@Override /* Overridden from JsonSchema */
public JsonSchemaRef setDefinitions(Map<String,JsonSchema> value) {
super.setDefinitions(value);
@@ -223,6 +235,12 @@ public class JsonSchemaRef extends JsonSchema {
return this;
}
+ @Override /* Overridden from JsonSchema */
+ public JsonSchemaRef setFormat(String value) {
+ super.setFormat(value);
+ return this;
+ }
+
@Override /* Overridden from JsonSchema */
public JsonSchemaRef setEnum(List<Object> value) {
super.setEnum(value);
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/package-info.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/package-info.java
index 57ec89ce0c..8c087c243c 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/package-info.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/package-info.java
@@ -47,7 +47,17 @@
* <li>{@link org.apache.juneau.bean.jsonschema.JsonSchemaArray} - An
array of <c>JsonSchema</c> objects.
* <li>{@link org.apache.juneau.bean.jsonschema.JsonType} - An enum of
possible JSON data types.
* <li>{@link org.apache.juneau.bean.jsonschema.JsonTypeArray} - An array
of <c>JsonType</c> objects.
+ * <li>{@link org.apache.juneau.bean.jsonschema.JsonSchemaBeanGenerator} -
Generates typed
+ * <c>JsonSchema</c> beans from Java types via {@link
org.apache.juneau.jsonschema.JsonSchemaGenerator}.
* </ul>
+
+ * <h5 class='topic'>Automatic Schema Generation</h5>
+ *
+ * <p>
+ * Use {@link org.apache.juneau.bean.jsonschema.JsonSchemaBeanGenerator} when
you want typed DTO output from POJOs
+ * without manually building schema trees. It bridges the map-based output of
+ * {@link org.apache.juneau.jsonschema.JsonSchemaGenerator} into
<c>JsonSchema</c> beans.
+ * </p>
*
* <h5 class='topic'>Creating JSON Schema Documents</h5>
*
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator_Test.java
new file mode 100644
index 0000000000..edcf88238a
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator_Test.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.bean.jsonschema;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.lang.reflect.*;
+import java.net.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.jsonschema.*;
+import org.junit.jupiter.api.*;
+
+class JsonSchemaBeanGenerator_Test extends TestBase {
+
+ @Test void a01_primitives() {
+ var g = JsonSchemaBeanGenerator.DEFAULT;
+ assertEquals("integer",
g.generate(int.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals("int32", g.generate(int.class).getFormat());
+ assertEquals("number",
g.generate(float.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals("float", g.generate(float.class).getFormat());
+ assertEquals("boolean",
g.generate(boolean.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals("string",
g.generate(String.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals("uri", g.generate(URI.class).getFormat());
+ }
+
+ @Test void a02_bean_collection_map_enum() {
+ var g = JsonSchemaBeanGenerator.DEFAULT;
+ assertEquals("object",
g.generate(SimpleBean.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertNotNull(g.generate(SimpleBean.class).getProperty("name"));
+ assertEquals("array",
g.generate(BeanList.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals("object",
g.generate(BeanList.class).getItemsAsSchema().getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals(Boolean.TRUE,
g.generate(BeanSet.class).getUniqueItems());
+
assertNotNull(g.generate(BeanMap.class).getAdditionalPropertiesAsSchema());
+ assertEquals("string",
g.generate(TinyEnum.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals(List.of("one","two"),
g.generate(TinyEnum.class).getEnum());
+ }
+
+ @Test void a03_useBeanDefs_injectsDefsAtRoot() {
+ var bean =
JsonSchemaBeanGenerator.create().useBeanDefs().build().generate(SimpleBean.class);
+ assertEquals("#/definitions/SimpleBean",
bean.getRef().toString());
+ assertNotNull(bean.getDefs());
+ assertTrue(bean.getDefs().containsKey("SimpleBean"));
+ }
+
+ @Test void a05_descriptions_and_examples() {
+ var bean = JsonSchemaBeanGenerator.create()
+ .addDescriptionsTo(TypeCategory.ANY)
+ .addExamplesTo(TypeCategory.ANY)
+ .allowNestedDescriptions()
+ .allowNestedExamples()
+ .build()
+ .generate(ExampleBean.class);
+
+ assertEquals("object",
bean.getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+
assertEquals("org.apache.juneau.bean.jsonschema.JsonSchemaBeanGenerator_Test$ExampleBean",
bean.getDescription());
+ assertEquals("java.lang.String",
bean.getProperty("value").getDescription());
+ }
+
+ @Test void a06_roundTripParityWithJsonMapOutput() throws Exception {
+ var map =
JsonSchemaGenerator.DEFAULT.getSession().getSchema(SimpleBean.class);
+ var bean = JsonSchemaBeanGenerator.toBean(map);
+ var mapJson = JsonSerializer.DEFAULT.serialize(map);
+ var beanJson = JsonSerializer.DEFAULT.serialize(bean);
+ assertEquals(
+ JsonParser.DEFAULT.parse(mapJson, JsonMap.class),
+ JsonParser.DEFAULT.parse(beanJson, JsonMap.class)
+ );
+ }
+
+ @Test void a07_staticOfFactories() {
+ assertEquals("object",
JsonSchema.of(SimpleBean.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+
assertNotNull(JsonSchema.of(SimpleBean.class).getProperty("name"));
+ assertEquals("object",
JsonSchema.of((java.lang.reflect.Type)SimpleBean.class).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+
assertNotNull(JsonSchema.of((java.lang.reflect.Type)SimpleBean.class).getProperty("name"));
+ }
+
+ @Test void a08_toBeanConversion() {
+ var map =
JsonMap.ofJson("{type:'integer',format:'int32','$comment':'c',deprecated:true}");
+ var bean = JsonSchemaBeanGenerator.toBean(map);
+ assertEquals("integer",
bean.getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertEquals("int32", bean.getFormat());
+ assertEquals("c", bean.getComment());
+ assertEquals(Boolean.TRUE, bean.getDeprecated());
+ }
+
+ @Test void b01_generateObjectPathAndIgnoredTypeNull() {
+ var g = JsonSchemaBeanGenerator.DEFAULT;
+ assertEquals("object", g.generate(new
SimpleBean()).getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ }
+
+ @Test void b01a_generateIgnoredTypeReturnsNull() {
+ var ignored =
JsonSchemaBeanGenerator.create().ignoreTypes("SimpleBean").build();
+ assertNull(ignored.generate(SimpleBean.class));
+ assertNull(ignored.generate(new SimpleBean()));
+ }
+
+ @Test void b01b_generateObjectWithBeanDefsAddsDefs() {
+ var bean =
JsonSchemaBeanGenerator.create().useBeanDefs().build().generate(new
SimpleBean());
+ assertNotNull(bean.getDefs());
+ assertTrue(bean.getDefs().containsKey("SimpleBean"));
+ }
+
+ @Test void b01c_generateTypeWithBeanDefsButNoDefinitions() {
+ var bean =
JsonSchemaBeanGenerator.create().useBeanDefs().build().generate(int.class);
+ assertEquals("integer",
bean.getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertNull(bean.getDefs());
+ }
+
+ @Test void b01d_generateObjectWithBeanDefsButNoDefinitions() {
+ var bean =
JsonSchemaBeanGenerator.create().useBeanDefs().build().generate(1);
+ assertEquals("integer",
bean.getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
+ assertNull(bean.getDefs());
+ }
+
+ @Test void b02_builderDelegates() {
+ var g = JsonSchemaBeanGenerator.create()
+ .addDescriptionsTo(TypeCategory.ANY)
+ .addExamplesTo(TypeCategory.ANY)
+ .allowNestedDescriptions()
+ .allowNestedDescriptions(false)
+ .allowNestedExamples()
+ .allowNestedExamples(false)
+ .beanDefMapper(TestBeanDefMapper.class)
+ .ignoreTypes("com.example.DoesNotExist")
+ .useBeanDefs()
+ .useBeanDefs(false)
+ .build();
+ assertNotNull(g.generate(SimpleBean.class));
+ }
+
+ @Test void b03_argumentValidation() {
+ assertThrows(RuntimeException.class, () ->
JsonSchemaBeanGenerator.DEFAULT.generate((Type)null));
+ assertThrows(RuntimeException.class, () ->
JsonSchemaBeanGenerator.DEFAULT.generate((Object)null));
+ assertThrows(RuntimeException.class, () ->
JsonSchemaBeanGenerator.toBean(null));
+ }
+
+ public static class SimpleBean {
+ public String name;
+ }
+
+ public static class ExampleBean {
+ public String value;
+
+ @Example
+ public static ExampleBean example() {
+ var x = new ExampleBean();
+ x.value = "v";
+ return x;
+ }
+ }
+
+ public static class BeanList extends LinkedList<SimpleBean> {
+ private static final long serialVersionUID = 1L;
+ }
+ public static class BeanSet extends LinkedHashSet<String> {
+ private static final long serialVersionUID = 1L;
+ }
+ public static class BeanMap extends LinkedHashMap<String,SimpleBean> {
+ private static final long serialVersionUID = 1L;
+ }
+
+ public static class TestBeanDefMapper extends BasicBeanDefMapper {}
+
+ public enum TinyEnum {
+ ONE,
+ TWO;
+
+ @Override
+ public String toString() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
index 63a70cfce8..88d879ec28 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
@@ -568,4 +568,25 @@ public class JsonSchema_Test extends TestBase {
// resolve = true path (no schemaMap, so resolve() returns self)
assertNotNull(x.getProperty("foo", true));
}
+
+ @Test void b25_formatCommentDeprecated_roundTrip() throws Exception {
+ var s = Json5Serializer.create().ws().build();
+ var p = Json5Parser.DEFAULT;
+
+ var x = new JsonSchema()
+ .setType(JsonType.STRING)
+ .setFormat("uri")
+ .setComment("schema note")
+ .setDeprecated(true);
+
+ var r = s.serialize(x);
+ assertTrue(r.contains("format: 'uri'"));
+ assertTrue(r.contains("'$comment': 'schema note'"));
+ assertTrue(r.contains("deprecated: true"));
+
+ var x2 = p.parse(r, JsonSchema.class);
+ assertEquals("uri", x2.getFormat());
+ assertEquals("schema note", x2.getComment());
+ assertEquals(Boolean.TRUE, x2.getDeprecated());
+ }
}
\ No newline at end of file
diff --git a/todo/TODO-13-system-properties-to-settings-conversion.md
b/todo/FINISHED-13-system-properties-to-settings-conversion.md
similarity index 99%
rename from todo/TODO-13-system-properties-to-settings-conversion.md
rename to todo/FINISHED-13-system-properties-to-settings-conversion.md
index 83194dbf45..4de241a74c 100644
--- a/todo/TODO-13-system-properties-to-settings-conversion.md
+++ b/todo/FINISHED-13-system-properties-to-settings-conversion.md
@@ -120,7 +120,7 @@ Verify `Utils.env(name, CacheMode.FULL)` works (Settings
supports Enum via value
### Phase 3: Special Cases
8. **ParameterInfo.java** - Uses `ResettableSupplier` for testability. Options:
- - Replace with
`Settings.get().get("juneau.disableParamNameDetection").asBoolean()`
+ - Replace with
`Settings.get().get("juneau.disableParamNameDetection").asBoolean()`
- Use `Setting.reset()` when test needs to re-read - check if Setting has
reset
- Keep ResettableSupplier but have it delegate to Settings
diff --git a/todo/TODO-21-bean-annotations-inject-package.md
b/todo/FINISHED-21-bean-annotations-inject-package.md
similarity index 100%
rename from todo/TODO-21-bean-annotations-inject-package.md
rename to todo/FINISHED-21-bean-annotations-inject-package.md
diff --git a/todo/TODO-8-jsonschema-bean-generation.md
b/todo/FINISHED-8-jsonschema-bean-generation.md
similarity index 97%
rename from todo/TODO-8-jsonschema-bean-generation.md
rename to todo/FINISHED-8-jsonschema-bean-generation.md
index f38c6a6df7..65ad5e662d 100644
--- a/todo/TODO-8-jsonschema-bean-generation.md
+++ b/todo/FINISHED-8-jsonschema-bean-generation.md
@@ -4,6 +4,14 @@
Add the ability for `JsonSchemaGenerator` to produce typed `JsonSchema` beans
instead of only `JsonMap` objects. This requires filling gaps in the
`JsonSchema` bean, creating a bridge class in the `juneau-bean-jsonschema`
module (which already depends on `juneau-marshall`), and comprehensive testing.
+## Execution Status
+
+- [x] Phase 1: Add Missing Properties to JsonSchema Bean
+- [x] Phase 2: Create JsonSchemaBeanGenerator
+- [x] Phase 3: Testing
+- [x] Phase 4: Documentation
+- [x] Phase 5: Verify and archive
+
## Problem
`JsonSchemaGenerator` (in `juneau-marshall`) produces `JsonMap` objects. There
is no way to obtain typed `JsonSchema` beans (in `juneau-bean-jsonschema`) from
the generator. Users who want structured, type-safe schema objects must
manually construct them or do ad-hoc conversion.
diff --git a/todo/TODO.md b/todo/TODO.md
index 5fb2d9a748..65e8153ee5 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -11,8 +11,6 @@
- [TODO-7] Decouple `juneau-rest-common` from `juneau-marshall` by breaking
the compile dependency so REST annotations and beans can be used without
pulling in the full serialization stack. See
`todo/TODO-7-decouple-rest-common-from-marshall.md`.
-- [TODO-8] Add typed `JsonSchema` bean output to `JsonSchemaGenerator`
(currently returns only `JsonMap`). Requires filling gaps in the `JsonSchema`
bean and adding a bridge class in `juneau-bean-jsonschema`. See
`todo/TODO-8-jsonschema-bean-generation.md`.
-
- [TODO-9] Fix remaining skipped Markdown round-trip test cases (tables,
nested structures, edge cases). See `todo/TODO-9-markdown-remaining-issues.md`.
- [TODO-10] Move `org.apache.juneau.http.annotation` from `juneau-marshall`
into `juneau-rest-common` (already done for the annotation classes — plan
tracks remaining follow-on cleanup). See
`todo/TODO-10-move-http-annotation-to-rest-common.md`.
@@ -27,5 +25,3 @@
- [TODO-30] Investigate moving `ClassMeta` and related non-marshalling type
metadata from `juneau-marshall` into `juneau-commons` (analysis/feasibility
pass). See `todo/TODO-30-classmeta-to-commons.md`.
-- [TODO-32] YAML support in juneau-config: add a YAML-format alternative to
the existing INI-style Config. New `ConfigStore` implementation reading/writing
`.yml` / `.yaml` files with parity for sections, keys, defaults, comments, and
SVL interpolation; round-trips edits without losing comments where possible.
-