FasterXML schema generation for Maps and Object references - jackson

I have an issue generating a JSON Schema file with FasterXML.
The file output just shows
object type for a Map<String, String>
null type for OtherBean
{
"type": "object",
"properties": {
"beanId": {
"type": "integer"
},
"beanName": {
"type": "string"
},
"beanMap": {
"type": "object"
},
"otherBean": null
} }
My Schema generation class
import java.io.File;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsonschema.JsonSchema;
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper MAPPER = new ObjectMapper();
JsonSchema jsonSchema = MAPPER.generateJsonSchema(MyBean.class);
MAPPER.writeValue(new File("MyBeanSchema.json"), jsonSchema);
}
}
MyBeans:
import java.util.Map;
public class MyBean {
private Integer beanId;
private String beanName;
private Map<String, String> beanMap;
private OtherBean otherBean;
public MyBean() {
}
public Integer getBeanId() {
return beanId;
}
public void setBeanId(Integer beanId) {
this.beanId = beanId;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public Map<String, String> getBeanMap() {
return beanMap;
}
public void setBeanMap(Map<String, String> beanMap) {
this.beanMap = beanMap;
}
public OtherBean getOtherBean() {
return otherBean;
}
public void setOtherBean(OtherBean otherBean) {
this.otherBean = otherBean;
}
}
OtherBean:
public class OtherBean {
}

Not directly answering your question, but Schema Generation is moving to a separate module:
https://github.com/FasterXML/jackson-module-jsonSchema/
which will have better functionality, and can evolve faster than old in-built generation.
So if possible, try using that. And then you can file bugs against this, for problems with generation.

Related

Value Dependent Deserialization with Jackson

I want to deserialize into a data structure. Dependent on the version of the JSON data I want to deserialize into different implementations of the same interface. And this works so far with a custom deserializer.
However, in the data structure I use references. And I expect that when undefined references are encountered an exception is thrown. The way I programmed it, this does not work together with the interface.
I created a small example with a (currently not passing) test case to show the desired behavior.
Additional Information:
In the test case, when I use concrete classes (instead of the interface) in readValue the desired behavior occurs. That is, when I write mapper.readValue(buggy, Database2.class); instead of mapper.readValue(buggy, DatabaseI.class);. But then I lose the ability to abstract from the particular content of the JSON data.
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.btc.adt.pop.scen.objectstreams.Person;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.IntNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
public class Example {
#Test
public void test() throws JsonProcessingException {
ObjectMapper mapper =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
SimpleModule module = new SimpleModule();
module.addDeserializer(DatabaseI.class, new ToyDeserializer());
mapper.registerModule(module);
String correct = "{'version':1,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['b']}]}";
DatabaseI deserCorrect = mapper.readValue(correct, DatabaseI.class);
System.out.println(mapper.writeValueAsString(deserCorrect));
String buggy = "{'version':2,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['FOO']}]}";
assertThrows(Exception.class, () -> {
mapper.readValue(buggy, DatabaseI.class);
}, "The reference FOO is undefined. An Exception should be thrown.");
}
}
class Person {
#JsonProperty("id")
private String id;
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#JsonIdentityReference(alwaysAsId = true)
private List<Person> friends = new ArrayList<>();
public Person() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<Person> getFriends() {
return friends;
}
public void setFriends(List<Person> friends) {
this.friends = friends;
}
}
interface DatabaseI {
}
class Database1 implements DatabaseI {
private int version;
private List<Person> people = new ArrayList<>();
public Database1() {
}
public List<Person> getPeople() {
return people;
}
public void setPeople(List<Person> people) {
this.people = people;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}
class Database2 implements DatabaseI {
private String version;
private List<Person> people = new ArrayList<>();
public Database2() {
}
public List<Person> getPeople() {
return people;
}
public void setPeople(List<Person> people) {
this.people = people;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
class ToyDeserializer extends StdDeserializer<DatabaseI> {
protected ToyDeserializer(Class<?> vc) {
super(vc);
}
public ToyDeserializer() {
this(null);
}
#Override
public DatabaseI deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode node = mapper.readTree(jp);
int version = (Integer) ((IntNode) node.get("version")).numberValue();
if (version == 1) {
return mapper.treeToValue(node, Database1.class);
} else {
return mapper.treeToValue(node, Database2.class);
}
}
}
This very good question! If you want to understand why no exception is thrown, your class Person must look like this:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = Person.class,
resolver = SimpleObjectIdResolverThrowsException.class
)
#JsonIdentityReference
class Person {
String id;
List<Person> friends = new ArrayList<>();
#ConstructorProperties({"id"})
public Person(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<Person> getFriends() {
return friends;
}
public void setFriends(List<Person> friends) {
this.friends = friends;
}
}
class SimpleObjectIdResolverThrowsException extends SimpleObjectIdResolver {
public SimpleObjectIdResolverThrowsException() {
super();
}
#Override
public Object resolveId(ObjectIdGenerator.IdKey id) {
if (this._items == null) {
return null;
}
Object obj = this._items.get(id);
if (obj == null) {
throw new RuntimeException("Unresolved reference for: " + id);
}
return obj;
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new SimpleObjectIdResolverThrowsException();
}
}
Now you can set break point in the method resolveId and see what happens when we de-serialize the string "{'version':1,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['b']}]}":
The problem is that the objects are processed one after the other and the references from the friends list are not resolved at that time.

How to assert/validate the JSON body and properties returned by a Micronaut controller

I am a Micronaut/Java beginner and I am trying to design some tests for my controllers. I could not find many examples online so here is my question.
Below is the controller with 2 #GET requests:
#Controller("/api/v1")
public class MyController {
private final ClientNetworkList clientNetworkList;
private final ClientStatus clientStatus;
public MyController(
ClientNetworkList clientNetworkList,
ClientStatus clientStatus
){
this.ClientNetworkList = clientNetworkList;
this.ClientStatus = clientStatus;
}
#Get(uri = "/networkList", produces = MediaType.APPLICATION_JSON_STREAM)
Flowable<NetworkListPackage> packagesNetworkList() {
return ClientNetworkList.fetchPackages();
}
#Get(uri = "/channels/{stringParm}/status/", produces = MediaType.APPLICATION_JSON_STREAM)
Flowable<ChannelStatusPackage> packagesStatus(stringParm) {
return ClientStatus.fetchPackages(genesis);
}
}
The java object POJOs:
#Introspected
public class NetworkListPackage {
private List<NetworkList> networkList = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public List<NetworkList> getNetworkList() {
return networkList;
}
public void setNetworkList(List<NetworkList> networkList) {
this.networkList = networkList;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
public class NetworkList {
private String name;
private Boolean authEnabled;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getAuthEnabled() {
return authEnabled;
}
public void setAuthEnabled(Boolean authEnabled) {
this.authEnabled = authEnabled;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
#Introspected
public class ChannelStatusPackage {
private String chaincodeCount;
private String txCount;
private String latestBlock;
private String peerCount;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public String getChaincodeCount() {
return chaincodeCount;
}
public void setChaincodeCount(String chaincodeCount) {
this.chaincodeCount = chaincodeCount;
}
public String getTxCount() {
return txCount;
}
public void setTxCount(String txCount) {
this.txCount = txCount;
}
public String getLatestBlock() {
return latestBlock;
}
public void setLatestBlock(String latestBlock) {
this.latestBlock = latestBlock;
}
public String getPeerCount() {
return peerCount;
}
public void setPeerCount(String peerCount) {
this.peerCount = peerCount;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
And the potential tests:
#MicronautTest
class MyControllerTest {
#Inject
#Client("/")
RxStreamingHttpClient client;
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
//when:
HttpRequest request = HttpRequest.GET("/api/v1/channels/{stringParam}/status/");
Flowable<ChannelStatusPackage> channelStatusPackageStream = client.jsonStream(request, ChannelStatusPackage.class);
Iterable<ChannelStatusPackage> channelStatusPackages = channelStatusPackageStream.blockingIterable();
//then:
//How to assert the returned body compared to the POJO?
//How to handle the parameter in the request url?
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
//when:
HttpRequest request = HttpRequest.GET("/api/v1/networkList");
Flowable<NetworkListPackage> networkListPackageStream = client.jsonStream(request, NetworkListPackage.class);
Iterable<NetworkListPackage> networkListPackages = networkListPackageStream.blockingIterable();
//then:
//How to assert the returned body and compared to the POJO?
//How to assert the returned properties ?
}
}
Based on the previous code, how can I test that the returned bodies and properties of the requests matches the POJOs?
What are the usual test to be carried out?
Thank you very much for helping.
Normaly, the basic assertion start by testing the object type, so this should validate your schema.
An other way to test it is to use RestAssured, witch is a bit more readable.
You need to import the fallowing dependencies in you build.gradle
testImplementation("io.rest-assured:rest-assured:4.2.+")
testImplementation("io.rest-assured:json-schema-validator:4.2.+")
You need test annotation processor to enable micronaut injection and junit 5 for the BeforeEach.
The full test dependencies:
testAnnotationProcessor("io.micronaut:micronaut-inject-java")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("io.micronaut.test:micronaut-test-junit5")
testImplementation("io.rest-assured:rest-assured:4.2.+")
testImplementation("io.rest-assured:json-schema-validator:4.2.+")
testRuntime("org.junit.jupiter:junit-jupiter-engine")
Then you can wright your tests like that:
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import io.micronaut.http.HttpStatus;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import io.restassured.RestAssured;
import javax.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
#MicronautTest
class MyControllerTest {
#Inject
private EmbeddedServer embeddedServer;
#BeforeEach
public void setUp() {
RestAssured.port = embeddedServer.getPort();
}
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.pathParam("stringParam", "value")
.get("/api/v1/channels/{stringParam}/status/")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(
"chaincodeCount", equalTo("chaincodeCountValue"),
"txCount", equalTo("txCountValue"),
"latestBlock", equalTo("latestBlockValue"),
"peerCount", equalTo("peerCountValue"),
"additionalProperties.key1", equalTo("additionalPropertyValue1"),
"additionalProperties.key2", equalTo("additionalPropertyValue2")
);
}
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.get("/api/v1/networkList")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(
"networkList.name[0]", equalTo("nameValue0"),
"networkList.authEnabled[0]", equalTo("authEnabledValue0"),
"networkList.additionalProperties[0].key1", equalTo("additionalPropertiesValue1"),
"networkList.additionalProperties[0].key2", equalTo("additionalPropertyValue2")
);
}
}
This is not really the way you wanted to do your tests, but I hope it will help.
So I ended up using the "hasItems" matcher or/and the jackson schema matcher.
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import io.micronaut.http.HttpStatus;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import io.restassured.RestAssured;
import javax.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.hasItems;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
#MicronautTest
class MyControllerTest {
#Inject
private EmbeddedServer embeddedServer;
#BeforeEach
public void setUp() {
RestAssured.port = embeddedServer.getPort();
}
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.pathParam("stringParam", "value")
.get("/api/v1/channels/{stringParam}/status/")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(matchesJsonSchemaInClasspath("channelsStatus.json"))
.body("keySet()",hasItems(
"chaincodeCount",
"txCount",
"latestBlock",
"peerCount",
);
}
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.get("/api/v1/networkList")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(matchesJsonSchemaInClasspath("networkList.json"))
.body("networkList.keySet()",hasItems(
"name",
"authEnabled",
);
}
}
``
Another option is to use jsonPath similar to Spring Boot MockMvc ResultMatcher:
testImplementation 'com.jayway.jsonpath:json-path:2.4.0'
testImplementation 'org.hamcrest:hamcrest:2.2'
Get the response as HttpResponse<String> and then use JsonPath.parse(response.body()) to assert the json path:
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
URI uri = UriBuilder.of("/api/v1/channels/{stringParam}/status/").expand(singletonMap("stringParam", "value"));
HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET(uri), String.class);
assertEquals(HttpStatus.OK, response.getStatus());
ReadContext ctx = JsonPath.parse(response.body());
assertThat(ctx.read("$"), isA(Object.class));
assertThat(ctx.read("$.chaincodeCount"), is("chaincodeCountValue"));
}
Example for an endpoint test using Micronaut vs Spring Boot

Jackson-Serialiser: Ignore Field at Serialisation Time

My situation asks for a bit more complex serialisation. I have a class Available (this is a very simplified snippet):
public class Available<T> {
private T value;
private boolean available;
...
}
So a POJO
class Tmp {
private Available<Integer> myInt = Available.of(123);
private Available<Integer> otherInt = Available.clean();
...
}
would normally result in
{"myInt":{available:true,value:123},"otherInt":{available:false,value:null}}
However, I want a serialiser to render the same POJO like this:
{"myInt":123}
What I have now:
public class AvailableSerializer extends JsonSerializer<Available<?>> {
#Override
public void serialize(Available<?> available, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException, JsonProcessingException {
if (available != null && available.isAvailable()) {
jsonGenerator.writeObject(available.getValue());
}
// MISSING: nothing at all should be rendered here for the field
}
#Override
public Class<Available<?>> handledType() {
#SuppressWarnings({ "unchecked", "rawtypes" })
Class<Available<?>> clazz = (Class) Available.class;
return clazz;
}
}
A test
#Test
public void testSerialize() throws Exception {
SimpleModule module = new SimpleModule().addSerializer(new AvailableSerializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
System.out.println(objectMapper.writeValueAsString(new Tmp()));
}
outputs
{"myInt":123,"otherInt"}
Can anyone tell me how to do the "MISSING"-stuff? Or if I'm doing it all wrong, how do I do it then?
The restriction I have is that I don't want the developers to add #Json...-annotations all the time to fields of type Available. So the Tmp-class above is an example of what a typical using class should look like. If that's possible...
Include.NON_DEFAULT
If we assume that your clean method is implemented in this way:
class Available<T> {
public static final Available<Object> EMPTY = clean();
//....
#SuppressWarnings("unchecked")
static <T> Available<T> clean() {
return (Available<T>) EMPTY;
}
}
You can set serialisation inclusion to JsonInclude.Include.NON_DEFAULT value and it should skip values set to EMPTY (default) values. See below example:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import java.io.IOException;
public class JsonApp {
public static void main(String[] args) throws Exception {
SimpleModule module = new SimpleModule();
module.addSerializer(new AvailableSerializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
System.out.println(objectMapper.writeValueAsString(new Tmp()));
}
}
class AvailableSerializer extends JsonSerializer<Available<?>> {
#Override
public void serialize(Available<?> value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
jsonGenerator.writeObject(value.getValue());
}
#Override
#SuppressWarnings({"unchecked", "rawtypes"})
public Class<Available<?>> handledType() {
return (Class) Available.class;
}
}
Above code prints:
{"myInt":123}
Custom BeanPropertyWriter
If you do not want to use Include.NON_DEFAULT you can write your custom BeanPropertyWriter and skip all values you want. See below example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
SimpleModule module = new SimpleModule();
module.addSerializer(new AvailableSerializer());
module.setSerializerModifier(new BeanSerializerModifier() {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> writers = new ArrayList<>(beanProperties.size());
for (BeanPropertyWriter writer : beanProperties) {
if (writer.getType().getRawClass() == Available.class) {
writer = new SkipNotAvailableBeanPropertyWriter(writer);
}
writers.add(writer);
}
return writers;
}
});
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
System.out.println(objectMapper.writeValueAsString(new Tmp()));
}
}
class AvailableSerializer extends JsonSerializer<Available<?>> {
#Override
public void serialize(Available<?> value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
jsonGenerator.writeObject(value.getValue());
}
#Override
#SuppressWarnings({"unchecked", "rawtypes"})
public Class<Available<?>> handledType() {
return (Class) Available.class;
}
}
class SkipNotAvailableBeanPropertyWriter extends BeanPropertyWriter {
SkipNotAvailableBeanPropertyWriter(BeanPropertyWriter base) {
super(base);
}
#Override
public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
// copier from super.serializeAsField(bean, gen, prov);
final Object value = (_accessorMethod == null) ? _field.get(bean) : _accessorMethod.invoke(bean, (Object[]) null);
if (value == null || value instanceof Available && !((Available) value).isAvailable()) {
return;
}
super.serializeAsField(bean, gen, prov);
}
}
Above code prints:
{"myInt":123}
After Michał Ziober's answer I had to look for something regarding Include.NON_DEFAULT and the default object and ran into this answer explaining Include.NON_EMPTY that Google didn't return in my first research (thanks Google).
So things become easier, it's now:
public class AvailableSerializer extends JsonSerializer<Available<?>> {
#Override
public void serialize(Available<?> available, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException, JsonProcessingException {
jsonGenerator.writeObject(available.getValue());
}
#Override
public Class<Available<?>> handledType() {
#SuppressWarnings({ "unchecked", "rawtypes" })
Class<Available<?>> clazz = (Class) Available.class;
return clazz;
}
#Override
public boolean isEmpty(SerializerProvider provider, Available<?> value) {
return value == null || !value.isAvailable();
}
}
with the test
#Test
public void testSerialize() throws Exception {
SimpleModule module = new SimpleModule().addSerializer(availableSerializer);
objectMapper.registerModule(module);
objectMapper.configOverride(Available.class).setInclude(
// the call comes from JavaDoc of objectMapper.setSerializationInclusion(...)
JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, JsonInclude.Include.ALWAYS));
Tmp tmp = new Tmp();
assertThat(objectMapper.writeValueAsString(tmp)).isEqualTo("{\"myInt\":123}");
tmp.otherInt.setValue(123);
assertThat(objectMapper.writeValueAsString(tmp)).isEqualTo("{\"myInt\":123,\"otherInt\":123}");
}
So please, if you upvote my answer please also upvote Michał Ziober's as that's also working with a mildly different approach.

Swagger Request custom class as Number

I have extended the BigDecimal class as a Premium class, because I want to add some default behavior (i.e. the MathContext). In the created swagger.json a BigDecimal is presented as:
"schema": {
"type": "number"
}
while the Premium class is presented as:
"schema": {
"$ref": "#/definitions/Premium"
}
and:
"definitions": {
"Premium": {
"type": "object"
}
}
How can I annotate the Premium class so that it is represented in the swagger.json as a "type": "number"?
The Premium class looks as follows:
public class Premium extends BigDecimal {
private static final long serialVersionUID = 1L;
private static final MathContext DEFAULT_MC = MathContext.DECIMAL64;
public Premium(String val) {
super(val);
}
#JsonCreator
public Premium(#JsonProperty BigDecimal bd) {
this(bd.toString());
}
public Premium multiply(Premium val) {
return new Premium(super.multiply(val));
}
public Premium divide(Premium val) {
return new Premium(super.divide(val, DEFAULT_MC));
}
public Premium subtract(Premium val) {
return new Premium(super.subtract(val));
}
}
And I created a REST service that looks as follows:
#Path("/test")
#Api
public class PremiumSvc {
#POST
#Path("/pr")
#ApiOperation("Premium.")
public Premium doPr(Premium req) {
return req.subtract(new Premium("0.02"));
}
#POST
#Path("/bd")
#ApiOperation("BigDecimal.")
public BigDecimal doBd(BigDecimal req) {
return req.subtract(new BigDecimal("0.02"));
}
}

JsonIgnore and JsonProperty not behaving as I expect

I have a simple class that I want to deserialize into JSON using Jackson. I want to rename one field logically in my JSON and the other I want to have the same name as defined in my Java class.
#JsonSerialize(include = Inclusion.NON_NULL)
public static class Manifest {
public Manifest(){
this.files = new ArrayList<String>();
}
#JsonProperty("manifest-version")
private String manifestVersion;
private ArrayList<String> files;
#JsonIgnore
public String getManifestVersion() {
return manifestVersion;
}
#JsonIgnore
public void setManifestVersion(String manifestVersion) {
this.manifestVersion = manifestVersion;
}
public ArrayList<String> getFiles() {
return files;
}
public void setFiles(ArrayList<String> files) {
this.files = files;
}
public void addFile(String file) {
this.files.add(file);
}
}
I'm expecting the #JsonIgnore for the getter/setter to cause manifestVersion to not become a JSON property (But should create a JSON property for manifest-version, where I have the #JsonProperty defined.
Expected output is
{
"manifest-version" : "2.0"
}
Actual output is
{
"manifest-version" : "2.0",
"manifestVersion":"2.0"
}
Any help would be appreciated.
I tried executing your code with Jackson 2.2 and i'm getting the expected output
import java.util.ArrayList;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize.Inclusion;
public class Test {
#JsonSerialize(include = Inclusion.NON_NULL)
public static class Manifest {
public Manifest(){
this.files = new ArrayList<String>();
}
#JsonProperty("manifest-version")
private String manifestVersion;
private ArrayList<String> files;
#JsonIgnore
public String getManifestVersion() {
return manifestVersion;
}
#JsonIgnore
public void setManifestVersion(String manifestVersion) {
this.manifestVersion = manifestVersion;
}
public ArrayList<String> getFiles() {
return files;
}
public void setFiles(ArrayList<String> files) {
this.files = files;
}
public void addFile(String file) {
this.files.add(file);
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper obj = new ObjectMapper();
Manifest m = new Manifest();
m.setManifestVersion("2.0");
System.out.println(obj.writeValueAsString(m));
}
}
Output: {"files":[],"manifest-version":"2.0"}
what version of jackson are you using?