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.
Related
I use jackson (jackson-databinder & jackson-dataformat-yaml) to serialize polymorphism classes to both json and yaml. And I use one class property as type resolver. I can remove the class metadata info in json, but in yaml it still contain the class meta info in tag. How can I remove that. Here's my sample code:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = Car.class, name = "car"),
#Type(value = Truck.class, name = "truck") })
public interface Vehicle {
String getName();
}
#Value
public static class Car implements Vehicle {
String name;
String type = "car";
#JsonCreator
public Car(#JsonProperty("name") final String name) {
this.name = requireNonNull(name);
}
}
#Value
public static class Truck implements Vehicle {
String name;
String type = "truck";
#JsonCreator
public Truck(#JsonProperty("name") final String name) {
this.name = requireNonNull(name);
}
}
#Value
public static class Vehicles {
List<Vehicle> vehicles;
#JsonCreator
public Vehicles(#JsonProperty("vehicles") final List<Vehicle> vehicles) {
super();
this.vehicles = requireNonNull(vehicles);
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper MAPPER = new ObjectMapper();
ObjectMapper YAML_MAPPER = YAMLMapper.builder()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
.build();
final Vehicles vehicles = new Vehicles(ImmutableList.of(new Car("Dodge"), new Truck("Scania")));
final String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(vehicles);
System.out.println(json);
final String yaml = YAML_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(vehicles);
System.out.println(yaml);
}
And here's json and yaml output:
{
"vehicles" : [ {
"name" : "Dodge",
"type" : "car"
}, {
"name" : "Scania",
"type" : "truck"
} ]
}
vehicles:
- !<car>
name: "Dodge"
type: "car"
- !<truck>
name: "Scania"
type: "truck"
There's no class meta info in json output. But in yaml, there're still the tag which contains the class meta info. Is it possible to remove that in yaml as json? Thanks
You can disable the YAMLGenerator.Feature#USE_NATIVE_OBJECT_ID yaml feature that is enabled by default for indicate types in the serialization. So in the construction of your ObjectMapper YAML_MAPPER mapper you can disable this feature like below obtaining the expected result:
ObjectMapper YAML_MAPPER = YAMLMapper.builder()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
.disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID)
.build();
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.
I want to write a spring-boot program to get values of name, id, and key where abc.active is true. I have written some code
#Repository
public interface SwitchRepoDao extends MongoRepository< SwitchRepo, String> {
public List<SwitchRepo> findByAbc_active(boolean active);
}
also, I have written class for interface.
#Document(collection="switchrepo")
public class SwitchRepo{
#Id
private String id;
private String type;
private List<Abc> abc;
// with getters and setters also constructors.
And Abc is class.
public class Abc{
private String name;
private String id;
private String key;
private boolean active;
This is the code I am using to display output.
#Bean
CommandLineRunner runner(SwitchRepoDao switchRepoDao) {
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
Iterable<SwitchRepo> personList = switchRepoDao.findAllWithStatus(true);
System.out.println("Configuration : ");
for (SwitchRepo config : personList)
{
System.out.println(config.getRegistries().toString());
}
}
};
}
Can anyone please help me with this. For any query related question do comment. Thank You in advance.
Given below is MongoDB Collection from database test. and collection name is switchrepo.
"_id" : "1234567890",
"type" : "xyz",
"abc" : [
{
"name" : "test",
"id" : "test1",
"key" : "secret",
"active" : true
},
{
"name" : "test2",
"id" : "test12",
"key" : "secret2",
"active" : false
}
]
}
In response, I need output as
"id" : "test1",
"key" : "secret",
"active" : true
because active is true in that sub-document array.
Actual Result what I got is "abc" : [{"name" : "test","id" : "test1","key" : "secret","active" : true},{"name" : "test2","id" : "test12","key" : "secret2","active" : false}]
You cannot use property-expressions for a proprety when the the field type is an Array.
here solutions
using the #Query or Aggregations
Solution 1 (Using #Query)
#Repository
public interface SwitchRepoDao extends MongoRepository< SwitchRepo, String> {
//public List<SwitchRepo> findByAbc_active(boolean active);
#Query(value = "{ 'abc.active' : ?0}", fields = "{ 'abc' : 1 }")
List<SwitchRepo> findAllWithStatus(Boolean status);
}
{ 'abc.active' : ?0} for filtring
{ 'abc' : 1 } for only return that part of the document (abc).
Calling findAllWithStatus will return all SwitchRepo with at least one ABC with active is true, you need to filter (using java 8 streams filter for examples all no active Abc from array)
Solution 2 (Using Mongodb aggregation)
Create a new dto class
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection="switchrepo")
public class SwitchRepoDto {
#Id
private String id;
private String type;
private Abc abc;
// with getters and setters also constructors.
public SwitchRepoDto() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Abc getAbc() {
return abc;
}
public void setAbc(Abc abc) {
this.abc = abc;
}
}
Create custom method Add custom method to Repository or inject MongoOperations into your service layer.
#Autowired
private MongoOperations mongoOperations;
public List<SwitchRepoDto> findAllActive() {
UnwindOperation unwind = Aggregation.unwind("$abc");
MatchOperation match = Aggregation.match(Criteria.where("abc.active").is(true));
Aggregation aggregation = Aggregation.newAggregation(unwind,match);
AggregationResults<SwitchRepoDto> results = mongoOperations.aggregate(aggregation, SwitchRepoDto.class, SwitchRepoDto.class);
List<SwitchRepoDto> mappedResults = results.getMappedResults();
return mappedResults;
}
I want to optimize the json data to be sent on wire. I have three models in my code. These are Customer, Invoice and Particular.
The Customer class
#Data
public class Customer implements Serializable {
private long customerId;
private String name;
private String taxId;
private String phone;
private String address;
private String emailId;
private Date created;
private List<Invoice> invoices;
}
The Invoice class
#Data
public class Invoice implements Serializable {
private String invoiceId;
private List<Particular> particulars;
private Date invoiceDate;
}
The Particular class
#Data
public class Particular {
private String item;
private int quantity;
private float tax;
private int unitPrice;
}
My test code:
#Test
public void makeCustomerJsonWithInvoices() throws JsonProcessingException {
Customer customer = new Customer();
customer.setCustomerId(1234);
customer.setName("Pawan");
customer.setPhone("+918989898989");
customer.setEmailId("something#something.com");
customer.setAddress("Mumbai, India");
customer.setTaxId("MQZ11DPS");
customer.setCreated(new Date());
Invoice invoice1 = new Invoice();
invoice1.setInvoiceId("A-1");
Particular particular1 = new Particular("abc", 1, 0, 12);
Particular particular2 = new Particular("xyz", 2, 0, 20);
invoice1.setInvoiceDate(new Date());
invoice1.setParticulars(Arrays.asList(particular1, particular2));
Particular particular3 = new Particular("mno", 2, 0, 15);
Invoice invoice2 = new Invoice();
invoice2.setInvoiceId("A-2");
invoice2.setParticulars(Arrays.asList(particular3));
invoice2.setInvoiceDate(new Date());
customer.setInvoices(Arrays.asList(invoice1, invoice2));
String value = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
System.out.println(value);
}
What I want here is to avoid the redundancy by serializing the Invoice so that the resulting json would be compact. This should be achieved by only sending the invoiceId attribute value instead of whole Invoice object json.
What the test code prints:
{
"customerId" : 1234,
"name" : "Pawan",
"taxId" : "MQZ11DPS",
"phone" : "+918989898989",
"address" : "Mumbai, India",
"emailId" : "something#something.com",
"created" : 1553243962038,
"invoices" : [ {
"invoiceId" : "A-1",
"particulars" : [ {
"item" : "abc",
"quantity" : 1,
"tax" : 0.0,
"unitPrice" : 12
}, {
"item" : "xyz",
"quantity" : 2,
"tax" : 0.0,
"unitPrice" : 20
} ],
"invoiceDate" : 1553243962038
}, {
"invoiceId" : "A-2",
"particulars" : [ {
"item" : "mno",
"quantity" : 2,
"tax" : 0.0,
"unitPrice" : 15
} ],
"invoiceDate" : 1553243962039
} ]
}
What I want it to print:
{
"customerId" : 1234,
"name" : "Pawan",
"taxId" : "MQZ11DPS",
"phone" : "+918989898989",
"address" : "Mumbai, India",
"emailId" : "something#something.com",
"created" : 1553243962038,
"invoices" : [ {
"invoiceId" : "A-1"
}, {
"invoiceId" : "A-2"
} ]
}
The #Data is lombok annotation used to generate getters and setters.
I tried to add #JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "invoiceId") annotation to Invoice class but this doesn't change the output.
Note that I want this serialization with Invoice happen only when it is passed as a child to a container Model. If I want to send Invoice independently, it shall serialize all fields in Invoice model. I believe this is a common scenario while implementing RESTful WS.
Do I need to write customer serializer for this?
I am able to achieve this by modifying the Customer class in following way.
#Data
public class Customer implements Serializable {
private long customerId;
private String name;
private String taxId;
private String phone;
private String address;
private String emailId;
private Date created;
#JsonIdentityInfo(generator= ObjectIdGenerators.PropertyGenerator.class, property="invoiceId")
#JsonIdentityReference(alwaysAsId=true)
private List<Invoice> invoices;
}
The answer is inspired from https://stackoverflow.com/a/17583175/1365340
With this I can generate Customer json with invoice Id list. The Invoice object when serialized separately gets all values from all its fields in json.
You can use #JsonIgnore for ingnoring properties in JSON response.
Or you can use transient keyword for avoiding serialization
Consider the following bean as a slight modification of your case:
#Data
#JsonFilter("idOnlyFilter")
#AllArgsConstructor
class Complex {
private String id;
private List<String> aList;
private Date aDate;
}
You could use the #JsonFilter concept to define really granular, on every bean you want, what the conditions for serializing are. Pay especially attention to the filter name idOnlyFilter in the ObjectMapper configuration as well as in the #JsonFilter annotation.
This works as shown below:
#Test
public void includeOnlyOneField() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider()
.addFilter("idOnlyFilter", SimpleBeanPropertyFilter.filterOutAllExcept("id"));
Complex complex = new Complex("a string", List.of(), new Date());
// when
String complexAsString = mapper.writer(filters).writeValueAsString(complex);
// then
assertThat(complexAsString).isEqualTo("{\"id\":\"a string\"}");
}
You can use #JsonAutoDetect on Invoice class to serialize only invoiceId field, e.g.:
#JsonAutoDetect(
fieldVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE,
getterVisibility = Visibility.NONE,
isGetterVisibility = Visibility.NONE,
creatorVisibility = Visibility.NONE
)
#Data
public class Invoice implements Serializable {
#JsonProperty (access = READ_ONLY)
private String invoiceId;
private List<Particular> particulars;
private Date invoiceDate;
}
This will make sure only invoiceId goes through the wire, have a look at the documentation here.
Update
If you want to have this behaviour only when Invoice is sent as nested object then you can set the other fields to null (or not set those fields in the first place) and use #JsonInclude annotation, e.g.:
#JsonInclude(Include.NON_NULL)
#Data
public class Invoice implements Serializable {
..
}
I am using Jackson library to try and parse my JSON file. My JSON is actually an ARRAY of JSON Objects:
JSON ARRAY:
[
{
"Id" : "0",
"name" : "John"
},
{
"Id" : "1",
"name" : "Doe"
}
]
POJO CLASS:
#JsonIgnoreProperties(ignoreUnknown = true)
public class QuestData {
private String Id;
private String name;
public String getId() {
return Id;
}
public String getName() {
return name;
}
}
PARSING JSON:
private void parseJSON(File jsonFile) {
try {
byte[] jsonData = Files.readAllBytes(jsonFile.toPath());
System.out.println(new String(jsonData));
ObjectMapper mapper = new ObjectMapper();
List<QuestData> questDataList = mapper.readValue(jsonData, mapper.getTypeFactory().constructCollectionType(List.class, QuestData.class));
System.out.println("Read values: " + questDataList.get(0).getId());
} catch (IOException e) {
e.printStackTrace();
}
}
My first print statement prints correct Json data back (as String).
But next print statement says NULL. I even tried to itreate over entire list ot see if there is something that is not null, but with no luck.
I do not know what I am doing wrong here.
Jackson will by default use setter methods to set fields. So add setters like:
#JsonProperty("Id") // otherwise Jackson expects id for setId
public void setId(String id) {
Id = id;
}
Alternatively, tell Jackson to look for fields with this config:
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
In this case Jackson will match the name of the field in the class Id with the one in JSON Id
Just add the #JsonProperty annotation to the Id property in your QuestData class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class QuestData {
#JsonProperty("Id")
private String Id;
private String name;
public String getId() {
return Id;
}
public String getName() {
return name;
}
}