Skip to content

Commit

Permalink
feat: Option.ACCEPT_SINGLE_VALUE_AS_ARRAY (#460)
Browse files Browse the repository at this point in the history
  • Loading branch information
CarstenWickner authored Jul 20, 2024
1 parent 21faa6b commit 70c473b
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 138 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### `jsonschema-generator`
#### Added
- new `Option.ACCEPT_SINGLE_VALUE_AS_ARRAY` to support Jackson `DeserializationFeature` of the same name, i.e., when an array type is declared, an instance of a single item should also be accepted by the schema

#### Changed
- consider `Boolean` values as valid in `const`/`enum` (i.e., no longer ignore them)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.victools.jsonschema.generator.MemberScope;
import com.github.victools.jsonschema.generator.Option;
import com.github.victools.jsonschema.generator.OptionPreset;
import com.github.victools.jsonschema.generator.SchemaGenerationContext;
import com.github.victools.jsonschema.generator.SchemaGenerator;
Expand All @@ -42,22 +43,13 @@ public class SingleArrayItemExample implements SchemaGenerationExampleInterface
@Override
public ObjectNode generateSchema() {
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON);
configBuilder.forFields()
.withTargetTypeOverridesResolver(this::acceptSingleValueAsArray);
configBuilder.with(Option.ACCEPT_SINGLE_VALUE_AS_ARRAY);
SchemaGeneratorConfig config = configBuilder.build();
SchemaGenerator generator = new SchemaGenerator(config);
return generator.generateSchema(Example.class);
}

private List<ResolvedType> acceptSingleValueAsArray(MemberScope<?, ?> scope) {
if (scope.isContainerType() && !scope.isFakeContainerItemScope()) {
return Arrays.asList(scope.getContainerItemType(), scope.getType());
}
return null;
}

static class Example {
@NotNull
public List<ArrayItem> someArray;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.github.victools.jsonschema.generator.impl.module.MethodExclusionModule;
import com.github.victools.jsonschema.generator.impl.module.SimpleTypeModule;
import com.github.victools.jsonschema.generator.impl.module.SimplifiedOptionalModule;
import com.github.victools.jsonschema.generator.impl.module.SingleValueAsArrayModule;
import java.util.Collections;
import java.util.Set;
import java.util.function.Supplier;
Expand Down Expand Up @@ -222,6 +223,14 @@ public enum Option {
* @since 4.11.0
*/
MAP_VALUES_AS_ADDITIONAL_PROPERTIES(AdditionalPropertiesModule::forMapValues, null),
/**
* Whether each property with a container/{@link java.util.Collection Collection} type should also allow for a single collection item to be
* provided instead of an array. This corresponds to the Jackson
* {@link com.fasterxml.jackson.databind.DeserializationFeature#ACCEPT_SINGLE_VALUE_AS_ARRAY ACCEPT_SINGLE_VALUE_AS_ARRAY} feature.
*
* @since 4.36.0
*/
ACCEPT_SINGLE_VALUE_AS_ARRAY(SingleValueAsArrayModule::new, null),
/**
* Whether allowed values should always be included in an {@link SchemaKeyword#TAG_ENUM "enum"} keyword. If there is exactly one allowed value, it
* will otherwise be represented by a {@link SchemaKeyword#TAG_CONST "const"} keyword instead.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2024 VicTools.
*
* Licensed 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 com.github.victools.jsonschema.generator.impl.module;

import com.fasterxml.classmate.ResolvedType;
import com.github.victools.jsonschema.generator.MemberScope;
import com.github.victools.jsonschema.generator.Module;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
import java.util.Arrays;
import java.util.List;

/**
* Default module being included if {@code Option.ACCEPT_SINGLE_VALUE_AS_ARRAY} is enabled.
*
* @since 4.36.0
*/
public class SingleValueAsArrayModule implements Module {

/**
* Allow a container's item type as single instance alternative to an array, or return null for non-containers.
*
* @param scope targeted field/method
* @return collection containing both the container's item type and the container type as such, or null
*/
private static List<ResolvedType> acceptSingleValueAsArray(MemberScope<?, ?> scope) {
if (scope.isContainerType() && !scope.isFakeContainerItemScope()) {
return Arrays.asList(scope.getContainerItemType(), scope.getType());
}
return null;
}

@Override
public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {
builder.forFields()
.withTargetTypeOverridesResolver(SingleValueAsArrayModule::acceptSingleValueAsArray);
builder.forMethods()
.withTargetTypeOverridesResolver(SingleValueAsArrayModule::acceptSingleValueAsArray);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ static Stream<Arguments> parametersForTestGenerateSchema() {
.withAnchorResolver(scope -> scope.isContainerType() ? null : "#anchor")
.withPropertySorter((_prop1, _prop2) -> 0),
"for type in general: ");
Module methodModule = configBuilder -> populateConfigPart(configBuilder.with(Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS)
Module methodModule = configBuilder -> populateConfigPart(configBuilder.with(Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS,
Option.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.forMethods(), "looked-up from method: ");
Module fieldModule = configBuilder -> populateConfigPart(configBuilder.with(Option.INLINE_ALL_SCHEMAS).forFields(), "looked-up from field: ");
Module enumToStringModule = configBuilder -> configBuilder.with(Option.FLATTENED_ENUMS_FROM_TOSTRING);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
"const": 5
},
"genericArray": {
"type": "array",
"items": {
"type": "string"
}
"anyOf": [
{
"type": "string"
}, {
"type": "array",
"items": {
"type": "string"
}
}
]
},
"genericValue": {
"type": ["string", "null"],
Expand Down
Loading

0 comments on commit 70c473b

Please sign in to comment.