Error converting Java interface to Json schema - java

I am trying to convert a java interface to json schema but it is giving NullPointerException
public interface Contributors {
public List<Contributor> contributors();
public interface Contributor {
public String name();
public String contributorUrl();
public List<String> roles();
}
}
Edit 2:
I am getting the following output:
{"type":"object","$schema":"http://json-schema.org/draft-04/schema#"}
Edit 3:
Following is the code of SchemaGeneratorTest
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.github.reinert.jjschema.exception.TypeException;
import com.github.reinert.jjschema.v1.JsonSchemaFactory;
import com.github.reinert.jjschema.v1.JsonSchemaV4Factory;
public class SchemaGeneratorTest {
private static ObjectMapper mapper = new ObjectMapper();
public static final String JSON_$SCHEMA_DRAFT4_VALUE = "http://json-schema.org/draft-04/schema#";
public static final String JSON_$SCHEMA_ELEMENT = "$schema";
static {
// required for pretty printing
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
public static void main(String[] args) throws JsonProcessingException, TypeException {
JsonSchemaFactory schemaFactory = new JsonSchemaV4Factory();
schemaFactory.setAutoPutDollarSchema(true);
JsonNode productSchema = schemaFactory.createSchema(Contributors.class);
System.out.println(productSchema);
}
}

The library you are using only reports fields and getters in your schema. Rename your methods to begin with get:
public interface Contributors {
public List<Contributor> getContributors();
}
public interface Contributor {
public String getName();
public String getContributorUrl();
public List<String> getRoles();
}
EDIT: If you can't modify the interfaces, you can use this code to corrupt the "get" string and get it to print all methods anyway. Please don't use it in real production code, as you will cause yourself a lot of trouble.
public class Test {
private static boolean isCorrupted() {
return "haha".startsWith("get");
}
public static void main(String[] args) throws Exception {
String get = "get";
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
value.set(get, new char[]{});
System.out.println(isCorrupted()); // prints true
}
}

Related

Map a JSON field (to a value) based on another field (which is a key) using Jackson

{
"key1": {
"parameter1": "String1",
"parameter2": "String2"
},
"key2": {
"parameter1": "String3",
"parameter2": "String4"
},
"key3": {
"parameter1": "String5",
"parameter2": "String6"
}
}
I have the above JSON (/Users/user1/Desktop/responseMap.json) which is basically a Map<String, MockResponse> where MockResponse is the below POJO:
public class MockResponse {
public String parameter1;
public String parameter2;
}
Now, I have another POJO - TestCase, and another JSON - testCase.json as below:
public class TestCase {
public String responseMapFileLocation;
public String mockResponseKey;
public MockResponse mockResponse;
}
testCase.json
{
"responseMapFileLocation": "/Users/user1/Desktop/responseMap.json",
"mockResponseKey": "key1",
"mockResponse": null
}
What I am able to do is first map testCase.json to TestCase using Jackson, then map responseMap.json to Map<String, MockResponse>, then in my code search for mockResponseKey in the map.
But what I want to do is when I map testCase.json to TestCase using Jackson, I want the value of variable mockResponse to set automatically based on the value of variable mockResponseKey using the first JSON map.
You need to write custom deserialiser for TestCase class. In custom deserialiser you can parse basic properties: responseMapFileLocation, mockResponseKey and load mockResponse from other file. To deserialiser MockResponse you can use new ObjectMapper instance. Below code shows how this concept could be implemented:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.MapType;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, TestCase.class));
}
}
class MockResponse {
public String parameter1;
public String parameter2;
}
#JsonDeserialize(using = TestCaseFromExternalFileDeserializer.class)
class TestCase {
public String responseMapFileLocation;
public String mockResponseKey;
public MockResponse mockResponse;
}
class TestCaseFromExternalFileDeserializer extends JsonDeserializer<TestCase> {
private final ObjectMapper mapper;
private final MapType mapType;
public TestCaseFromExternalFileDeserializer() {
mapper = new ObjectMapper();
mapType = mapper.getTypeFactory().constructMapType(Map.class, String.class, MockResponse.class);
}
#Override
public TestCase deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
TreeNode treeNode = p.readValueAsTree();
TestCase testCase = new TestCase();
testCase.responseMapFileLocation = ((JsonNode) treeNode.get("responseMapFileLocation")).asText();
testCase.mockResponseKey = ((JsonNode) treeNode.get("mockResponseKey")).asText();
parseMockResponse(testCase);
return testCase;
}
private void parseMockResponse(TestCase testCase) throws IOException {
Map<String, MockResponse> map = mapper.readValue(new File(testCase.responseMapFileLocation), mapType);
testCase.mockResponse = map.get(testCase.mockResponseKey);
}
}
You need to implement only toString method for each POJO class. Above code prints:
TestCase{responseMapFileLocation='./resource/responseMap.json', mockResponseKey='key1', mockResponse=MockResponse{parameter1='String1', parameter2='String2'}}
Both JSON files are in resource folder.
See also:
How use jackson ObjectMapper inside custom deserializer?
Jackson Streaming API - if you want to implement MockResponse deserialisation in faster way.
Tweaking the getter setter in your Test class and marking the field as private I was able to make it dynamic (Imports are from org.codehaus.jackson package)
class TestCase {
private String responseMapFileLocation;
private String mockResponseKey;
#JsonIgnore
private MockResponse mockResponse; //else value will be override in json value
public String getResponseMapFileLocation() {
return responseMapFileLocation;
}
public void setResponseMapFileLocation(String responseMapFileLocation) {
this.responseMapFileLocation = responseMapFileLocation;
}
public String getMockResponseKey() {
return mockResponseKey;
}
public void setMockResponseKey(String mockResponseKey1) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, MockResponse> map = mapper.readValue(new File("C:\\Users\\Json1.json"), TypeFactory.mapType(HashMap.class, String.class, MockResponse.class));
this.mockResponse = map.get(mockResponseKey1);
this.mockResponseKey = mockResponseKey1;
}
public MockResponse getMockResponse() {
return mockResponse;
}
#Override
public String toString() {
return "TestCase [responseMapFileLocation=" + responseMapFileLocation + ", mockResponseKey=" + mockResponseKey
+ ", mockResponse=" + mockResponse + "]";
}
}
class MockResponse {
public String parameter1;
public String parameter2;
#Override
public String toString() {
return "MockResponse [parameter1=" + parameter1 + ", parameter2=" + parameter2 + "]";
}
}
and Running below code
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
TestCase testCase = mapper.readValue(new File("C:\\UsersJson2.json"), TestCase.class);
System.out.println(testCase);
}
output will be
TestCase [responseMapFileLocation=/Users/user1/Desktop/responseMap.json, mockResponseKey=key1, mockResponse=MockResponse [parameter1=String1, parameter2=String2]]
What you are asking for is not possible with just Jackson. Jackson is primarily a marshalling/unmarshalling tool, converting JSONs to Objects and vice versa. In other words, the value of the object must be known at the time of unmarshalling.
However you can unmarshal your json as a HashMap using the code:
new JSONObject(map);
search for the MockResponse-as-a-string using the mockResponseKey and then unmarshal that code into a new MockResponse.

Jackson and generic types inside a subclass

I am rewriting the question since I figured out the actual error in the code.
This is a fully functional example of my issue (I am using Jackson 2.9.0):
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import java.io.IOException;
import java.net.URL;
import java.util.List;
public class MainClass {
public static class SubClass<TYPE> {
private List<TYPE> values;
public List<TYPE> getValues() {
return values;
}
public void setValues(List<TYPE> values) {
this.values = values;
}
}
public static class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private SubClass<Foo> propertyFoo;
public SubClass<Foo> getPropertyFoo() {
return propertyFoo;
}
public void setPropertyFoo(SubClass propertyFoo) {
this.propertyFoo = propertyFoo;
}
public static void main(String args[]) throws IOException {
URL url = System.class.getResource("/testFoo.json");
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.readerFor(MainClass.class);
MainClass mainClass = reader.readValue(url);
mainClass.getPropertyFoo().getValues().forEach(foo -> {
System.out.println(String.format("name: %s", foo.getName()));
});
}
}
Note the missing type parameter:
public void setPropertyFoo(SubClass propertyFoo)
instead of
public void setPropertyFoo(SubClass<Foo> propertyFoo)
The first form compiles but produces the following exception when run
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MainClass$Foo
As I wrote in the newly edited post, my issue was with that setter. Although it compiles it was throwing an exception since Jackson couldn't find the right type to use for the inner objects.
Fixing
public void setPropertyFoo(SubClass propertyFoo)
to
public void setPropertyFoo(SubClass<Foo> propertyFoo)
I know it's a trivial error but since I already spent all this time on finding the issue, maybe it will help somebody. If a mod/admin thinks this is not helpful, I can delete it whole.

environment.getArgument can't cast to my own java object in graphql-java

I Can't cast input object to DTO because of below error ExecutionStrategy.resolveField() - Exception while fetching data java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.fathome.graphql.OffersDto
OffersDto inputObject = environment.getArgument("offersInput");
please let me know what's wrong in below code, thanks in advance.
package com.fathome.graphql;
import graphql.schema.*;
import static graphql.Scalars.*;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLInputObjectField.newInputObjectField;
import static graphql.schema.GraphQLInputObjectType.newInputObject;
import static graphql.schema.GraphQLList.list;
import static graphql.schema.GraphQLObjectType.newObject;
public class ManualGraphQLQuerySchema {
public static GraphQLObjectType offersResponse = newObject()
.name("OffersResponse")
.field(newFieldDefinition()
.name("offerName")
.type(GraphQLString))
.field(newFieldDefinition()
.name("offerId")
.type(GraphQLString))
.build();
public static GraphQLInputObjectType offersRequestType = GraphQLInputObjectType.newInputObject()
.name("OffersDto")
.field(newInputObjectField()
.name("offerName")
.type(GraphQLString))
.field(newInputObjectField()
.name("offerId")
.type(GraphQLString))
.build();
public static GraphQLObjectType queryType = newObject()
.name("QueryType")
.field(newFieldDefinition()
.name("offers")
.type(offersResponse)
.argument(GraphQLArgument.newArgument()
.name("offersInput")
.type(offersRequestType))
.dataFetcher(new OffersFetcher()))
.build();
}
package com.fathome.graphql;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"offerName",
"offerId"
})
public class OffersDto {
#JsonProperty("offerName")
private String offerName;
#JsonProperty("offerName")
public String getOfferName() {
return offerName;
}
#JsonProperty("offerName")
public void setOfferName(String offerName) {
this.offerName = offerName;
}
#JsonProperty("offerId")
private String offerId;
#JsonProperty("offerId")
public String getOfferId() {
return offerId;
}
#JsonProperty("offerId")
public void setOfferId(String offerId) {
this.offerId = offerId;
}
}
package com.fathome.graphql;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
public class OffersFetcher implements DataFetcher<OffersDto> {
#Override
public OffersDto get(DataFetchingEnvironment environment) {
//Can't cast input object DTO this is error in below line
//ExecutionStrategy.resolveField() - Exception while fetching data
//java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.fathome.graphql.OffersDto
OffersDto inputObject = environment.getArgument("offersInput");
//calling service to get offerdetails using inputObject
//for testing not calling service just returning mock object.
OffersDto offersDto = new OffersDto();
offersDto.setOfferName("123");
offersDto.setOfferId("456");
return offersDto;
}
}
In below reference link similar to my code working fine.
Episode episode = environment.getArgument("episode");
ReviewInput review = environment.getArgument("review");
http://graphql-java.readthedocs.io/en/latest/execution.html
The values you get from environment.getArgument(...) will either be scalar values (strings, numbers etc) or a Map in case of an object GraphQL input type (like in your case).
You then need to do the deserialization yourself. Since you're using Jackson, it would look like this:
private final ObjectMapper objectMapper;
...
Object rawInput = environment.getArgument("offersInput");
OffersDto inputObject = objectMapper.convertValue(rawInput, OffersDto.class);
Check out graphql-java-tools for schema-first approach, or graphql-spqr for code-first, both make DataFetchers completely transparent, so no manual steps like above needed.

Jackson map different attributes based on json in the same class

I have a class Response which has an attribute data.
A json file is mapped to this object. The data attribute can be of type TaskData or SubmitData on the json.
If the json has the object of type TaskData the object mapper must map to TaskData class or should map to `SubmitData' class.
In addition to the answer by Guillaume Polet, and if you can modify the JSON schema, this can also be done a little bit smoother using Jackson's Polymorphic (de)serialization via annotations:
#JsonTypeInfo(use=JsonTypeInfo.Id.Class, include=JsonTypeInfo.As.PROPERTY, property="#class")
class Data {}
class TaskData extends Data {}
class SubmitData extends Data {}
This will write out the full Java class name as an additional #class property. The json needs to include the #class property on input, however.
Instead of JsonTypeInfo.Id.Class it is also possible to perform explicit naming
#JsonTypeInfo(use=JsonTypeInfo.Id.Class, include=JsonTypeInfo.As.PROPERTY, property="#dataType")
#JsonSubTypes({
JsonSubTypes.Type(value=TaskData.class, name="task"),
JsonSubTypes.Type(value=SubmitData.class, name="submit")
})
class Data {}
#JsonTypeName("task")
class TaskData extends Data {}
#JsonTypeName("submit")
class SubmitData extends Data {}
This will yield an additional synthetic field #dataType, which will need to be present in the input.
If you cannot make the type explicit in the input JSON, you will need to stick to the manual approach.
You need to type your Response class as follows: public class Response<T>.
Then, when deserializing the input, provide a TypeReference to jackson to indicate the desired type.
See this example:
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestJacksonTyping {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
Response<TaskData> taskResponse = new Response<TaskData>();
TaskData taskData = new TaskData();
taskData.setTaskTitle("Some title");
taskResponse.setData(taskData);
Response<SubmitData> submitResponse = new Response<SubmitData>();
SubmitData submitData = new SubmitData();
submitData.setSubmitValue(256);
submitResponse.setData(submitData);
StringWriter sw = new StringWriter();
mapper.writeValue(sw, taskResponse);
String taskResponseJson = sw.toString();
mapper.writeValue(sw = new StringWriter(), submitResponse);
String submitResponseJson = sw.toString();
Response<TaskData> deserializedTaskResponse = mapper.reader(new TypeReference<Response<TaskData>>() {
}).readValue(new StringReader(taskResponseJson));
Response<SubmitData> deserializedSubmitResponse = mapper.reader(new TypeReference<Response<SubmitData>>() {
}).readValue(new StringReader(submitResponseJson));
System.out.println(deserializedTaskResponse.getData().getTaskTitle());
System.out.println(deserializedSubmitResponse.getData().getSubmitValue());
}
public static class Response<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public static class TaskData {
private String taskTitle;
public String getTaskTitle() {
return taskTitle;
}
public void setTaskTitle(String taskTitle) {
this.taskTitle = taskTitle;
}
}
public static class SubmitData {
private int submitValue;
public int getSubmitValue() {
return submitValue;
}
public void setSubmitValue(int submitValue) {
this.submitValue = submitValue;
}
}
}

jackson Pascal Case and #JsonProperty on getter

i create a mapper with
new ObjectMApper()
.setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.setSerializationInclusion(Include.NON_NULL)
and serialization works perfectly on fields (no getters and setters). field currentStatus is serialized to "currentStatus" (first letter uppercase). but i have also one getter (without a field and setter) which must be camelCase. so i do:
#JsonProperty("abcDef")
public String getZxy() {...
but it is serialized to "AbcDef" instead of "abcDef". it looks like naming strategy still triggers and change the first letter. i use jackson-databind 2.3.2;
how can i map this getter with first letter lowercase?
EDIT:
ugly code, but shows the problem. this test should pass but it fails
import static org.assertj.core.api.Assertions.assertThat;
import org.testng.annotations.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
public class JsonFailureTest {
#Test
public void should_serialize_first_letter_lowercase() throws Exception {
String json = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.writeValueAsString(
new Object(){
#JsonProperty("fooBar")
public String whatever() {return "";}
});
assertThat(json).contains("fooBar");
}
}
Here's a workaround using a custom "annotation-aware" strategy:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
public class Foo {
public static void main(final String[] args) throws JsonProcessingException {
final SomeObject someObject = new SomeObject();
someObject.setZxy("foobar");
final ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PascalCaseStrategy() {
#Override
public String nameForGetterMethod(final MapperConfig<?> config, final AnnotatedMethod method, final String defaultName) {
final JsonProperty annotation = method.getAnnotation(JsonProperty.class);
if (annotation != null) {
return annotation.value();
}
return super.nameForGetterMethod(config, method, defaultName);
}
});
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
System.out.println(mapper.writeValueAsString(someObject));
}
private static class SomeObject {
private String zxy;
#JsonProperty("abcDef")
public String getZxy() {
return this.zxy;
}
public void setZxy(final String zxy) {
this.zxy = zxy;
}
}
}
Output:
{"abcDef":"foobar"}

Categories