i've a enum in my json-schema, which looks like this:
"definitions": {
"parentObject": {
"type": "object",
"properties": {
"child": {
"$ref": "#/definitions/childType"
}
}
},
"childType": {
"type": "string",
"enum": [
"BROTHER",
"SISTER",
"DAUGTHER"
]
}
}
I need to implement it as a java-enum.
#JsonTypeName("parentObject")
public class FahrtSegmentUtilizationType {
#JsonProperty(value = "child")
private childType child;
}
and i've tried out a lot of jackson-annotations, but nothing helps.
#JsonTypeName("childType")
//#JsonFormat(shape = JsonFormat.Shape.OBJECT)
//#JsonSerialize(using = ChildTypeSerializer.class)
public enum ChildType {
BROTHER("BROTHER"),
SISTER("SISTER"),
DAUGHTER("DAUGTHER")
private String child;
#JsonCreator
ChildType(String type) {
this.child = type;
}
//#JsonValue
public String getUtilazation() {
return this.child;
}
}
Everytime i got
"parentObject": {
"type": "object",
"properties": {
"child": {
"type" : "string",
"enum" : [
[
"BROTHER",
"SISTER",
"DAUGTHER"
]
]
}
}
}
With "#JsonFormat(shape = JsonFormat.Shape.OBJECT)" i got $ref on ChildType-Object, but this referenz-object stays empty.
How can i implement enum, so it looks like in wished schema?
Related
I'm trying to omit null values in my ResponseEntity.
My controller looks something like this:
#RestController
public class FooController {
//fields
//constructor
#PostMapping
public ResponseEntity<CreateFooResponseV10> createFoo(#Valid #RequestBody CreateFooRequestV10 foo, HttpServletRequest request) {
//some minor logic
return new ResponseEntity<>(aFooResponseV10Builder()
.withFirstName(foo.getFirstName())
.withLastName(foo.getLastName())
.withTestField(NULLABLE_OBJECT)
.build(), ...);
//I generated the builders from the output classes openapi-generator provided
}
// more stuff...
}
When NULLABLE_OBJECT is equal to null I expect the field to be omitted from the response like this:
{
"firstName": "John",
"lastName": "Doe"
}
But I either get these responses, depending on what I've tried so far:
{
"firstName": "John",
"lastName": "Doe",
"testField": null
}
or
{
"firstName": "John",
"lastName": "Doe",
"testField": {"present":false}
}
I generate my request/response objects (CreateFooResponseV10 and CreateFooRequestV10) with the use of openapi-generator
Here is my redacted api.json file:
{
"openapi": "3.0.1",
"info": { ... },
"servers": [ ... ],
"paths": {
"/foo": {
"post": {
...
"requestBody": {
"description": "Foo to be created",
"content": {
"application/foo+json;version=1.0": {
"schema": {
"$ref": "#/components/schemas/CreateFooRequest_V1_0"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Foo is successfully created",
"headers": { ... },
"content": {
"application/foo+json": {
"schema": {
"$ref": "#/components/schemas/CreateFooResponse_V1_0"
}
}
}
},
...
}
}
}
},
"components": {
"schemas": {
"CreateFooRequest_V1_0": {
"required": [
"firstName",
"lastName"
],
"type": "object",
"properties": {
"firstName": { ... },
"lastName": { ... },
"testField": {
"description": "...",
"type": "string",
"nullable": true
}
}
},
"CreateFooResponse_V1_0": {
"required": [
"firstName",
"lastName"
],
"type": "object",
"properties": {
"firstName": { ... },
"lastName": { ... },
"testField": {
"description": "...",
"type": "string",
"nullable": true
}
}
}
}
}
}
As you can see in both the request and response testField is not required and can be nullable.
So when testField is null it should be hidden from the response, but when it contains some date it should be shown of course.
I've tried overriding jackson's ObjectMapper bean as explained in this answer. Didn't work.
I've tried adding spring.jackson.default-property-inclusion=non_null to the application.properties. Didn't work.
What I think should work is adding #JsonIgnore above testField of the generated classes, but I don't know if this is something needed to be done manually (for each schema component, can be a lot of manual work for something that is generated) or if this can be configured in the plugin somewhere.
Thanks in advance.
extra info
OpenAPI 3.0.1
Maven 3.6.3
Java 11.0.2
jackson-databind-nullable 0.2.1
openapi-generator-maven-plugin 4.2.2
You can set the following in application.properties
spring.jackson.default-property-inclusion = NON_NULL
See Customize the Jackson ObjectMapper
Note: To make use of this, you need to #Autowire the ObjectMapper, and not manually create it
Try registering the following bean in your spring context. It should override default bean
#Bean
public HttpMessageConverters httpMessageConverters() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL)
return new HttpMessageConverters(
new MappingJackson2HttpMessageConverter(mapper));
}
You can generate the model classes with additional class annotations using OpenApi generator.
Just need to include this in your maven plugin:
<configOptions>
<additionalModelTypeAnnotations>
#com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
<additionalModelTypeAnnotations>
<configOptions>
see other config options here:
https://openapi-generator.tech/docs/generators/spring/
Try this code. I tested and it works.
#RestController
#RequestMapping("/testws")
public class TestWS {
#RequestMapping(value = "test", method = { RequestMethod.POST,
RequestMethod.GET }, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<TestBean> test(#Context HttpServletRequest request) {
TestBean testBean = new TestBean("John", "Doe", null);
return ResponseEntity.ok()
.body(testBean);
}
}
#JsonInclude(Include.NON_NULL)
class TestBean {
private String firstName;
private String lastName;
private String testField;
public TestBean() {
}
public TestBean(String firstName, String lastName, String testField) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.testField = testField;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getTestField() {
return testField;
}
public void setTestField(String testField) {
this.testField = testField;
}
}
Json response:
{"firstName":"John","lastName":"Doe"}
I am using the API to communicate batches of contacts using the endpoint /contacts/v1/contact/batch/
I get an error message response which reads#
{"status":"error", "message":"Invalid input JSON on line 1, column 1: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token","correlationId":"3c1488a8-24f5-4e1c-b506-18edcd870065","requestId":"a85c3ea88b60a7d0e3cfe5736c819b11"}
The JSON i am sending is valid. I've checked and double checked.
Please help :(
My output is below
[
{
"email": "twst#email.com",
"properties": [
{
"property": "company",
"value": "Test"
},
{
"property": "website",
"value": "www.test.com"
},
{
"property": "firstname",
"value": "Carl"
},
{
"property": "lastname",
"value": "Swann"
},
{
"property": "jobtitle",
"value": "Dr"
},
{
"property": "phone",
"value": "0789654321"
},
{
"property": "product",
"value": "Khaos Control Hybrid"
},
{
"property": "eventList_2019",
"value": "Spring Fair"
}
]
},
{
"email": "email#yes .com",
"properties": [
{
"property": "company",
"value": "Another one"
},
{
"property": "website",
"value": "www.a.ither.com"
},
{
"property": "firstname",
"value": "Anither"
},
{
"property": "lastname",
"value": "One"
},
{
"property": "jobtitle",
"value": "Com"
},
{
"property": "phone",
"value": "0789675341"
},
{
"property": "product",
"value": "Khaos Control Hybrid"
},
{
"property": "eventList_2019",
"value": "Spring Fair"
}
]
},
{
"email": "keeley#sophieallport.com",
"properties": [
{
"property": "company",
"value": "Sophie Allport"
},
{
"property": "website",
"value": "www.sophieallport.com"
},
{
"property": "firstname",
"value": "Keeley"
},
{
"property": "lastname",
"value": "Walters"
},
{
"property": "jobtitle",
"value": "Accounts "
},
{
"property": "phone",
"value": "01778235648"
},
{
"property": "product",
"value": "Khaos Control Hybrid"
},
{
"property": "eventList_2019",
"value": "Spring Fair"
}
]
}
]
I found the source code I used and will try my best to explain my implementation of it for you.
The application I created was a mobile app that is used by companies to capture information about new prospects and send that information into their HubSpot account.
The main application contains a class of Prospect which defines the information we want to obtain about a prospect
public class Prospect
{
public string CompanyName { get; set; }
public string Website { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Products { get; set; }
public string Notes { get; set; }
public string ContactOwner { get; set; }
public string ShowName { get; set; }
}
The component of the application which is relevant to the question you asked contains 2 classes:
public class HubSpotProspect
{
public string email { get; set; }
public List<Property> properties { get; set; }
}
public class Property
{
public string property { get; set; }
public string value { get; set; }
}
The following code gets a list of all the prospects then iterates through them to assign the correct attribute values and translates then into a new list of HubSpotProspect. We then serialise this list into json and pass it to the function that communicates with HubSpot API.
List<Prospect> _pList = _prospectList.GetProspectList(ShowName);
List _hsProspectList = new List();
foreach (Prospect p in _pList)
{
HubSpotProspect _hsp = new HubSpotProspect();
_hsp.email = p.Email;
_hsp.properties = new List<Property>();
_hsp.properties.Add(new Property { property = "company", value = p.CompanyName });
_hsp.properties.Add(new Property { property = "website", value = p.Website });
_hsp.properties.Add(new Property { property = "firstname", value = p.FirstName });
_hsp.properties.Add(new Property { property = "lastname", value = p.LastName });
_hsp.properties.Add(new Property { property = "jobtitle", value = p.JobTitle });
_hsp.properties.Add(new Property { property = "phone", value = p.Phone });
_hsp.properties.Add(new Property { property = "product", value = p.Products });
_hsp.properties.Add(new Property { property = "event_list_2019", value = p.ShowName });
_hsp.properties.Add(new Property { property = "hubspot_owner_id", value = _userProfile.GetContactOwner() });
_hsProspectList.Add(_hsp);
}
string json = JsonConvert.SerializeObject(_hsProspectList);
await SendContact(json);
The function that communicates with the HubSpot API is as follows:
private Task SendContact(string JsonString)
{
return Task.Factory.StartNew(() =>
{
string hapiKey = _userProfile.GetHapiKey();
var client = new RestClient(https://api.hubapi.com/);
var request = new RestRequest("contacts/v1/contact/batch/", Method.POST);
request.AddQueryParameter("hapikey", hapiKey);
request.RequestFormat = DataFormat.Json;
request.AddJsonBody(JsonString);
IRestResponse response = client.Execute<HubSpotProspect>(request);
var content = response.Content;
//Console.WriteLine("RESPONSE " + content);
});
}
I am trying to use Autofac Configuration to create a Service object.
public class Service : IService
{
public Service([KeyFilter("eod")]ISimpleMongoClient eodClient,
[KeyFilter("live")]ISimpleMongoClient liveClient
) : base(config)
{
_eodClient = eodClient;
_liveClient = liveClient;
}
}
public class SimpleMongoClient : ISimpleMongoClient
{
public SimpleMongoClient(string connectionString, string database)
{
IMongoClient client = new MongoClient(connectionString);
MongoDatabase = client.GetDatabase(database);
}
}
Somehow with the following configuration, it is not able to resolve the ISimpleMongoClient parameters correctly. What else am I missing?
{
"components": [
{
"type": "Service, TestProject",
"services": [
{
"type": "IService, TestProject"
}
],
"instanceScope": "single-instance"
},
{
"type": "SimpleMongoClient, TestProject",
"services": [
{
"type": "ISimpleMongoClient, TestProject",
"key": "eod"
}
],
"parameters": {
"connectionString": "mongodb://localhost:27017/?readPreference=primary",
"database": "eod"
},
"instanceScope": "single-instance"
},
{
"type": "SimpleMongoClient, TestProject",
"services": [
{
"type": "ISimpleMongoClient, TestProject",
"key": "live"
}
],
"parameters": {
"connectionString": "mongodb://localhost:27017/?readPreference=primary",
"database": "live"
},
"instanceScope": "single-instance"
}
]
}
To use the KeyFilter attribute you need to register the thing doing the filtering with the WithAttributeFiltering() extension. You can't do that through configuration.
I am getting the following exception after writing the below code.
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Root name 'Filing' does not match expected ('List') for type [collection type; class java.util.List, contains [simple type, class MasMonthlyReportDetail]]
JSON Object
{
"Filing":
[
{
"periodInfo":
{
"date": "06-05-2013",
"year": "2015",
"Month": "January"
},
"employerInfo":
{
"name": "Y",
"place": "Y",
"country": "N",
},
"employeeInfo":
[
{
"name": "785-23-0370",
"dob": "05/25/1952",
}
],
"messages":
[
{
"defaultMessage" : "xx",
"messageType" : "yy",
"messageCode" : "102"
}
]
},
{
"periodInfo":
{
"date": "06-05-2013",
"year": "2015",
"Month": "January"
},
"employerInfo":
{
"name": "Y",
"place": "Y",
"country": "N",
},
"employeeInfo":
[
{
"name": "785-23-0370",
"dob": "05/25/1952",
}
],
"messages":
[
{
"defaultMessage" : "xx",
"messageType" : "yy",
"messageCode" : "102"
}
]
}
]
}
Main Class
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
List<MasMonthlyReportDetail> lcd = objectMapper.readValue(new File(filePath),new TypeReference<List<MasMonthlyReportDetail>>(){});
MasMonthlyReportDetail.java
#JsonRootName(value="Filing")
public class MasMonthlyReportDetail {
private PeriodInfo periodInfo;
private EmployerInfo employerInfo;
List<EmployeeInfo> employeeInfo;
List<Messages> messages;
public PeriodInfo getPeriodInfo() {
return periodInfo;
}
public void setPeriodInfo(PeriodInfo periodInfo) {
this.periodInfo = periodInfo;
}
public EmployerInfo getEmployerInfo() {
return employerInfo;
}
public void setEmployerInfo(EmployerInfo employerInfo) {
this.employerInfo = employerInfo;
}
public List<EmployeeInfo> getEmployeeInfo() {
return employeeInfo;
}
public void setEmployeeInfo(List<EmployeeInfo> employeeInfo) {
this.employeeInfo = employeeInfo;
}
public List<Messages> getMessages() {
return messages;
}
public void setMessages(List<Messages> messages) {
this.messages = messages;
}
}
I made the following changes and the code worked for me.
Main Class:
InputStream inputStream = resource.getInputStream();
ObjectMapper objectMapper = new ObjectMapper();
MasMonthlyReportDetailHolder masMonthlyReportDetailHolder = objectMapper.readValue(inputStream, MasMonthlyReportDetailHolder.class);
List<MasMonthlyReportDetail> masMonthlyReportDetail = masMonthlyReportDetailHolder.getMasMonthlyReportDetail();
MasMonthlyReportDetailHolder class:
public class MasMonthlyReportDetailHolder {
private List<MasMonthlyReportDetail> masMonthlyReportDetail;
#JsonProperty("Filing")
public List<MasMonthlyReportDetail> getMasMonthlyReportDetail() {
return masMonthlyReportDetail;
}
public void setMasMonthlyReportDetail(
List<MasMonthlyReportDetail> masMonthlyReportDetail) {
this.masMonthlyReportDetail = masMonthlyReportDetail;
}
}
Adding #JsonProperty("Filing") is the key to avoid this issue. In case of any other procedure, do let me know.
have u tried this ?
jacksonObjectMapper.reader(MasMonthlyReportDetail.class).withRootName("Filing").readValue(jsonAsString);
I have the following Java Classes:
public class ModuleParsed {
String id_component;
String id_instance;
Map<ModuleParam, ModuleParam> input;
Map<ModuleParam, List<ModuleParam>> output;
int id_paas;
}
and
public class ModuleParam {
String name;
Object type;
}
what should be the JSON expected to parse it as a List<ModuleParsed>?
listModules = gson.fromJson(br, new TypeToken<List<ModuleParsed>>() {}.getType());
Everything was ok until I introduced the input and output parameters.
EDIT
By doing the reverse process I have found the JSON should be something like
[
{
"id_component": "mod1",
"id_instance": "mod1_inst1",
"input": {
"moduleParam": {
"name": "param3",
"type": "obj3"
},
"moduleParam": {
"name": "param2",
"type": "obj2"
}
},
"id_paas": 1
},
{
"id_component": "mod2",
"id_instance": "mod2_inst1",
"input": {
"moduleParam": {
"name": "param3",
"type": "obj3"
},
"moduleParam": {
"name": "param2",
"type": "obj2"
}
},
"id_paas": 1
}
]
where the moduleParam elements were something like: parser.ModuleParam#36c51089
No matter how I name these elements I get the following error:
Exception in thread "main" com.google.gson.JsonParseException: Expecting object found: "moduleParam"
How can I obtain the class ModuleParsed with Gson?
Following #Brian's comment I realized I had no need to use complex objects as keys so this was my solution.
public class ModuleParsed {
String id_component;
String id_instance;
Map<String, ModuleParam> input;
Map<String, List<ModuleParam>> output;
int id_paas;
}
public class ModuleParam {
String name;
String id_module;
String id_instance;
}
for which I built the following JSON
[
{
"id_component": "mod1",
"id_instance": "mod1_inst1",
"input": {
"input1": {}
},
"output": {
"output1": [
{
"name": "input1",
"id_module": "mod2",
"id_instance": "mod2_inst1",
"type": "paramType"
},
{
"name": "input2",
"id_module": "mod2",
"id_instance": "mod2_inst1",
"type": "paramType"
}
],
"output2": []
},
"id_paas": 1
},
{
"id_component": "mod2",
"id_instance": "mod2_inst1",
"input": {
"input1": {
"name": "output1",
"id_module": "mod1",
"id_instance": "mod1_inst1",
"type": "paramType"
},
"input2": {
"name": "output1",
"id_module": "mod1",
"id_instance": "mod1_inst1",
"type": "paramType"
}
},
"output": {},
"id_paas": 1
}
]
The JSON was parsed correctly this time.