I'm trying to send a HTTP request to an API which accepts a JSON request body in this format
{
"firstName" : "XYZ",
"family" : {
"commonDetails" : {
"secondName" : "ABC"
},
"1" : "Mother name",
"2" : "Father name",
"3" : "Spouse name"
}
}
So I have defined a request payload the below way.
public class UserDetails {
private String firstName;
private Map<String, AbstractFamilyDetails> details;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties("type")
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = FamilyCommonDetails.class, name = "COMMON_DETAILS"),
#JsonSubTypes.Type(value = FamilyIndividual.class, name = "FAMILY")
})
public abstract class AbstractFamilyDetails {
private String type;
}
#Data
#EqualsAndHashCode(callSuper = true)
public class FamilyCommonDetails extends AbstractFamilyDetails {
private String secondName;
#Builder
public FamilyCommonDetails(String secondName) {
super("COMMON_DETAILS");
this.secondName = secondName;
}
public FamilyCommonDetails() {
super("COMMON_DETAILS");
}
#Override
public String toString() {
return this.secondName;
}
}
#Data
#EqualsAndHashCode(callSuper = true)
public class FamilyIndividual extends AbstractFamilyDetails {
private String individual;
#Builder
public FamilyIndividual(String individual) {
super("FAMILY");
this.individual = individual;
}
public FamilyIndividual() {
super("FAMILY");
}
#Override
public String toString() {
return this.individual;
}
}
But when I debug what json payload I'm hitting the server with turns out to be
{
"firstName" : "XYZ",
"family" : {
"commonDetails" : {
"secondName" : "ABC",
},
"1" : {"individual": "Mother name"},
"2" : {"individual": "Father name"},
"3" : {"individual": "Spouse name"}
}
}
Where is it that I'm going wrong? Do I have to define a custom jackson deserialiser to achieve this?
Try the #JsonUnwrapped annotation on the individual field.
See: https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonUnwrapped.html
Have tried using #JsonUnwrapped as #user3296624 suggested. However it didn't work. There is an open issue on jackson-databind related to the same. Issue
Have tried #JsonAnySetter too, which did't help.
So, modified my request model to private Map<String, Object> details and discarded AbstractFamilyDetails as the last resort and used it accordingly. Not an ideal solution. But worked.
Related
I know we can ignore case for input JSON by adding property in application.yml as:
spring:
jackson:
mapper:
accept_case_insensitive_properties: true
But if my POJO extends an abstract class, it is not working and my JSON is not being parsed.
My abstract class:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "event")
#JsonSubTypes({
#JsonSubTypes.Type(value = Orders.class, name = "orders"),
#JsonSubTypes.Type(value = WorkOrders.class, name = "workOrders")
})
public abstract class ElasticDocument {
// Fields and getter/setter
}
My Pojo:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
public class Orders extends ElasticDocument {
//other fields
private List<OrderLine> orderLines;
}
Input JSON which I am getting from input has different case e.g.
{
"event": "orders",
"OrderNo": 12345,
"Status": "Created",
"CustomerZipCode": "23456",
"CustomerFirstName": "firstname1",
"orderType": "PHONEORDER",
"customerLastName": "lastname1",
"OrderLines": [
{
"LineName": "sample"
}
]
}
My contoller method where I am using this ElasticDocument object:
#PostMapping("save")
public Orders save(#RequestBody ElasticDocument elasticDocument) {
return elasticsearchRepository.save((Orders) elasticDocument);
}
I am using Spring-boot version 2.2.4
I think you forgot to add #type to your request JSON.#type is to identify the type of ElasticDocument being serialized.
Here is a example that i tried in my local system with minimum fields in class:
ElasticDocument.java
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(value = Orders.class, name = "Orders"),
#JsonSubTypes.Type(value = WorkOrders.class, name = "workOrders")
})
public abstract class ElasticDocument {
private Integer docId;
private String docName;
// getters and setters
}
Orders.java
public class Orders extends ElasticDocument{
private Integer orderId;
private String orderName;
// getters and setters
}
WorkOrders.java
public class WorkOrders extends ElasticDocument{
private Integer workOrderId;
private String workOrderName;
// getters and setters
}
StackOverflowController.java
#RestController
#RequestMapping("/api/v1")
public class StackOverflowController {
#PostMapping("/orders")
ElasticDocument createOrder(#RequestBody ElasticDocument order){
return order;
}
}
When i send data like this to my endpoint (Please note the attributes name in json are lowercase)
{
"#type":"workOrders",
"docId":123,
"docName":"XXXX",
"orderid":45,
"ordername":"shoe",
"workorderid":324,
"workordername":"dsf"
}
It is converted to workOrders response:
{
"#type": "workOrders",
"docId": 123,
"docName": "XXXX",
"workOrderId": 324,
"workOrderName": "dsf"
}
And when i changed the #type to Orders in request then i will get Order response:
{
"#type": "Orders",
"docId": 123,
"docName": "XXXX",
"orderId": 45,
"orderName": "shoe"
}
I made a code which add comments on my localhost:3000 but its parsing to much info i want to remove "commentModel" but if i remove it from CommentRq class i get errors
comment example:
{ "commentModel": { "comment": "komentarz", "date": "3/6/19 9:34 AM" }, "id": 1}
i want it to be { "comment": "komentarz", "date": "3/6/19 9:34 AM" }, "id": 1 }
CommentRq
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public class CommentRq {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private CommentModel commentModel;
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public static class CommentModel {
#JsonProperty("comment")
String resourceName;
#JsonProperty("date")
String resourceNamed;
}
}
CommentBody
public class CommentBody {
Date now = new Date();
#JsonInclude(JsonInclude.Include.NON_NULL)
public CommentRq RequestCommentBody() {
return CommentRq.builder()
.commentModel(new CommentRq.CommentModel(
"komentarz",
(DateFormat.getInstance().format(now))
))
.build();
}
}
Here i create comment
Interface.PostComment postComment = Feign.builder()
.client(new OkHttpClient())
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.logger(new Slf4jLogger(Interface.PostComment.class))
.logLevel(Logger.Level.FULL)
.target(Interface.PostComment.class, "http://localhost:3000/comments/");
#When("i try to add a comment")
public void postComment() {
Map<String, Object> headermap = new HashMap<>();
headermap.put("Content-Type", "application/json");
CommentBody requestComment = new CommentBody();
CommentRes commentRes = postComment.postComment(headermap, requestComment.RequestCommentBody());
id = commentRes.getId();
LOGGER.info("Created: " + DateFormat.getInstance().format(now));
}
You can annotate your private CommentModel commentModel with #JsonUnwrapped. It will unwrap your commentModel object and write its fields to the root of the json. This will handle your specific case. But you can revise your request structure as well: put CommentModel fields into CommentRq and map CommentModel object to CommentRq object.
I'm writing a REST API in Java and Play Framework, however I ran into a problem with Jackson serialization. I have the following model:
#Entity
#JsonRootName("country")
public class Country extends BaseModel<Country> {
private String name;
private Collection<City> cities;
...
}
The Jackson object mapper configuration:
ObjectMapper mapper = Json.newDefaultMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
When I serialize a Country model however,
Country c = service.get(id);
return ok(toJson(c));
I get the following output:
{
"ObjectNode" : {
"country" : {
"id" : 5,
"name" : "Holandija",
"cities" : [ ]
}
}
}
The expected output would be:
{
"country" : {
"id" : 5,
"name" : "Holandija",
"cities" : [ ]
}
}
Why is Jackson adding the extra ObjectNode node? How to get rid of it?
It seems you have a problem in toJson method. The following code works perfect (the original class Country was modified for simplicity):
#Entity
#JsonRootName(value = "country")
public class Country {
public int id;
public String name;
public Collection<String> cities;
public Country() {
}
public Country(int id, String name) {
this.id = id;
this.name = name;
}
}
Test:
#Test
public void testRootJsonMapping() throws JsonProcessingException {
Country tested = new Country(55, "Neverland");
ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
String json = mapper.writeValueAsString(tested);
System.out.println("json:" + json);
}
Test output:
json:{
"country" : {
"id" : 55,
"name" : "Neverland",
"cities" : null
}
}
If json conversion is done with Play API Json, it should be configured on startup with appropriate mapping options:
private void configureJson() {
ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
Json.setObjectMapper(mapper);
}
Here you can read more details of how to customize Json conversion in Play.
I am currently trying to develop a REST client that communicates with a certain online service. This online service returns some JSON responses which I wish to map to Java objects using Jackson.
An example of a JSON response would be:
{
"id" : 1,
"fields" : [ {
"type" : "anniversary",
"value" : {
"day" : 1,
"month" : 1,
"year" : 1970
}
}, {
"type" : "birthday",
"value" : {
"day" : 1,
"month" : 1,
"year" : 1970
}
}, {
"type" : "simple",
"value" : "simple string"
},{
"type": "name",
"value": {
"firstName": "Joe",
"lastName": "Brown"
}
} ]
}
NOTE the following:
this structure contains a simple id, and a collection of Field instances, each having a type and a value
the value structure is determined by the external property type
in the example given, there are 3 types -> date, name, and single value string
the birthday and anniversary types both match the date structure
there are several types which map to a single value string, like email type, twitterId type, company type etc.
My problem is that I do not seem to be able to correctly map this structure to the Java objects
Here's what I've done so far. The following are the classes and their Jackson annotations(the getters and setters are omitted).
public class Contact {
private int id;
private List<Field> fields;
}
public class Field {
private FieldType type;
private FieldValue value;
}
public enum FieldType {
EMAIL, NICKNAME, NAME, ADDRESS, BIRTHDAY, ANNIVERSARY
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type",
defaultImpl = SingleFieldValue.class)
#JsonSubTypes({
#JsonSubTypes.Type(value = NameFieldValue.class, name = "name"),
#JsonSubTypes.Type(value = DateFieldValue.class, name = "anniversary"),
#JsonSubTypes.Type(value = DateFieldValue.class, name = "birthday"),
#JsonSubTypes.Type(value = SingleFieldValue.class, name = "nickname"),
#JsonSubTypes.Type(value = SingleFieldValue.class, name = "email"),
//other types that map to SingleFieldValue
})
public abstract FieldValue {
}
public class NameFieldValue extends FieldValue {
private String firstName;
private String lastName;
}
public class DateFieldValue extends FieldValue {
private int day;
private int month;
private int year;
}
public class SingleFieldValue extends FieldValue {
private String value;
}
The ObjectMapper does not contain any configuration, the default configuration is used.
What suggestions do you have to correctly map these? I would like to avoid making custom deserializers and just traverse Json objects, like JsonNode.
NOTE: I apologize in advance for any lack of information to make this issue clear enough. Please state any problems with my formulation.
You have used an abstract class on the FieldValue level to use it in FIeld class. In that case you can construct the object with type=email and value=address which can lead to some issues...
I would recommend to create a specific classes for every type with specific FieldValue type.
The following code is serializing/deserializing JSON from/to required format from/to POJO:
public class Main {
String json = "{\"id\":1,\"fields\":[{\"type\":\"SIMPLE\",\"value\":\"Simple Value\"},{\"type\":\"NAME\",\"value\":{\"firstName\":\"first name\",\"lastName\":\"last name\"}}]}";
public static void main(String []args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(generate());
System.out.println(json);
System.out.println(objectMapper.readValue(json, Contact.class));
}
private static Contact generate() {
SimpleField simpleField = SimpleField.builder().type(FieldType.SIMPLE).value("Simple Value").build();
NameFieldValue nameFieldValue = NameFieldValue.builder().firstName("first name").lastName("last name").build();
NameField nameField = NameField.builder().type(FieldType.NAME).value(nameFieldValue).build();
return Contact.builder().id(1).fields(Arrays.asList(simpleField, nameField)).build();
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SimpleField.class, name = "SIMPLE"),
#JsonSubTypes.Type(value = NameField.class, name = "NAME")
})
interface Field {
FieldType getType();
Object getValue();
}
enum FieldType {
SIMPLE, NAME
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class Contact {
private int id;
private List<Field> fields;
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class SimpleField implements Field {
private FieldType type;
private String value;
#Override
public FieldType getType() {
return this.type;
}
#Override
public String getValue() {
return this.value;
}
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class NameField implements Field {
private FieldType type;
private NameFieldValue value;
#Override
public FieldType getType() {
return this.type;
}
#Override
public Object getValue() {
return this.value;
}
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class NameFieldValue {
private String firstName;
private String lastName;
}
I have used lombok library here just to minimize the code and avoiding creating getters/setters as well as constructors. You can delete lombok annotations and add getters/setters/constructors and code will work the same.
So, the idea is that you have a Contact class (which is root of your JSON) with a list of Fields (where Field is an interface). Every Field type has own implementation like NameField implements Field and has NameFieldValue as a property. The trick here is that you can change getValue() method declaration and declare that it returns the common interface or Object (I used Object but interface will work as well).
This solution doesn't require any custom serializers/deserializers and easy in maintenance.
All code below is simplified version.
I have JSON structure:
{
"content" : {
"elements" : [ {
"type" : "simple"
},
{
"type" : "complex",
"content" : {
"elements" : [ {
"type" : "simple"
},
{
"type" : "simple"
},
{
"type" : "complex",
"content" : {
---- /// ----
}
} ]
}
} ]
}
}
I use Jackson lib for deserialization, and i am trying to implement a kind of "mix" custom with default deserializers.
I want Element object creates using custom ElementDeserializer but for Content field inside use default. Unfortunately things like that:
#JsonDeserialize(using = StdDeserializer.class)
#JsonProperty
Content content;
isn't work =(
Here is my code now:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Content {
#JsonProperty("elements")
ArrayList<Element> mElements;
}
#JsonDeserialize(using = ElementDeserializer.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Element<T extends ElementType> {
#JsonProperty
Content content;
T mField;
public Element(T field) {
mField = field;
}
}
public class ElementDeserializer extends JsonDeserializer<Element> {
#Override
public Element deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Element element = null;
JsonNode node = jp.getCodec().readTree(jp);
if ("simple".equals(node.get("type").textValue())) {
element = new Element(new SimpleField());
} else if ("complex".equals(node.get("type").textValue())) {
element = new Element(new ComplexField());
}
return element;
}
}
I will be grateful for some help!
Not sure whether it is mandatory for you to use a custom deserializer (for reasons not indicated in your post). If it is not, then you can do without one, using the default deserializers.
Here is how:
#JsonIgnoreProperties(ignoreUnknown = true)
public class TopObject {
#JsonProperty
public Content content;
public TopObject() {
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Content {
#JsonProperty
public Element elements [];
public Content() {
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#Type(value = SimpleElement.class, name = "simple"),
#Type(value = ComplexElement.class, name = "complex")
})
public class Element {
public Element() {
}
}
public class SimpleElement extends Element {
public SimpleElement() {
}
}
public class ComplexElement extends Element {
#JsonProperty
public Content content;
public ComplexElement() {
}
}
Then unserialize the json data as a TopObject.class