Spring sends empty JSON despite of object being not null - json

In my controller I have the following method:
#RequestMapping(value = "/getAll", method = RequestMethod.GET)
public List<Topic> getAllTopics() {
List<Topic> allTopics = service.getAllTopics();
assert allTopics.size() > 0; // is not empty
System.out.println(allTopics.get(0)); // Topic{id=1, name='bla', description='blahhh'}
return allTopics;
}
When I go to http://localhost:8080/getAll I get [{},{},{},{}] as a result but service.getAllTopics() returns non empty List So the list to be send is not empty but the browser receives invalid JSON. However, there is no problem in serializing objects since the following method return valid JSON. What's the problem?
#GetMapping("/json")
public List<Locale> getLocales() {
return Arrays.asList(DateFormat.getAvailableLocales());
}
I'm running latest Spring Boot, i.e. 2.1.3.RELEASE.
Update
Here's my entity class - Topic
#Entity
#Table(name = "topic", schema="tetra")
public class Topic {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String description;
public Topic() {
}
public Topic(String name, String description) {
this.name = name;
this.description = description;
}
#Override
public String toString() {
return "Topic{" +
"id=" + id +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
}

By default , Jackson will only serialise the public fields and public getters into JSON. As the Topic neither have public fields nor the public getter , nothing will be serialised and you get an empty JSON object.
There are plenty of ways to configure it such as:
(1) Simply add public getter for all fields
(2) Use #JsonAutoDetect(fieldVisibility = Visibility.ANY) such that private fields can also be auto detected :
#Entity
#Table(name = "topic", schema="tetra")
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class Topic {
}
(3) Use #JsonProperty to explicitly pick what fields/getter to be serialised .The nice things of this approach is that the field name in JSON can be different from the POJO :
#Entity
#Table(name = "topic", schema="tetra")
public class Topic {
#JsonProperty("id")
private Integer id;
#JsonProperty("name")
private String name;
#JsonProperty("description")
private String description;
}

Related

jackson - avoid serializing getters

I have a class that has some additional getters for derived values. When I serialize this with jackson objectmapper, it writes out that field as well. Is there a way to avoid that?
example -
public class CustomPath implements Serializable {
private String path;
private String name;
private String extension = ".txt";
#JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public CustomPath(#JsonProperty("path") String path, #JsonProperty("name") String name) {
this.path = path;
this.name = name;
}
public String getPath()
{ return this.path;}
public void setPath(String path)
{ this.path = path;}
public String getName()
{ return this.name;}
public void setName(String name)
{ this.name = name;}
public String getExtension()
{ return this.extension;}
public void setExtension(String extension)
{ this.extension = extension;}
public String getFullPath() //do not want to serialize this
{ return this.path +"/" + this.name + this.extension;}
}
The json for a class like this looks like -
{
path: somepath
name: somename
extension: .txt
fullPath: somepath/somename.txt
}
But I do not want to serialize 'fullPath' in the example above. Is there any way I can avoid that?
You need to annotate the getFullPath() method with #JsonIgnore:
#JsonIgnore
public String getFullPath() // do not want to serialize this
{
return this.path + "/" + this.name + this.extension;
}
Then the JSON output will look like this:
{
"path" : "somePath",
"name" : "someName",
"extension" : ".txt"
}

JAX-RS/Jackson -- Deserialize JSON with Unknown Root Element Name?

I am writing a RESTeasy Proxy Client to consume Apple's API for retrieving their iTunes category list. When you query for information about a given category , for example with this URL:
https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/genres?id=1420
...you get a JSON response that looks like this:
{
"1420":{
"name":"Self-Help",
"id":"1420",
"url":"https://itunes.apple.com/us/genre/podcasts-health-self-help/id1420?mt=2",
"rssUrls":{
"topVideoPodcastEpisodes":"https://itunes.apple.com/us/rss/topvideopodcastepisodes/genre=1420/json",
"topAudioPodcasts":"https://itunes.apple.com/us/rss/topaudiopodcasts/genre=1420/json",
"topVideoPodcasts":"https://itunes.apple.com/us/rss/topvideopodcasts/genre=1420/json",
"topPodcasts":"https://itunes.apple.com/us/rss/toppodcasts/genre=1420/json",
"topAudioPodcastEpisodes":"https://itunes.apple.com/us/rss/topaudiopodcastepisodes/genre=1420/json",
"topPodcastEpisodes":"https://itunes.apple.com/us/rss/toppodcastepisodes/genre=1420/json"
},
"chartUrls":{
"videoPodcastEpisodes":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=VideoPodcastEpisodes",
"podcasts":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=Podcasts",
"audioPodcastEpisodes":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=AudioPodcastEpisodes",
"audioPodcasts":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=AudioPodcasts",
"podcastEpisodes":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=PodcastEpisodes",
"videoPodcasts":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=VideoPodcasts"
}
}
}
I am trying to map this JSON response to a Java object using JAXB and Jackson. However, the "1420" root element name seems to be causing a problem, as I get the following exception when calling my client:
Unrecognized field "1420" (class foo.bar.ITunesCategoryList), not marked as ignorable
My JAXB class looks like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ITunesCategory implements TransferObject {
private static final long serialVersionUID = 3443545925023804457L;
#XmlElement(name = "id")
#JsonProperty("id")
private String identifier = null;
#XmlElement
private String name = null;
#XmlElementWrapper(name = "subgenres")
private List<ITunesCategory> subcategories = new ArrayList<ITunesCategory>();
...
}
I've even tried creating a wrapper class since the search could result in more than one category being returned. It looks like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ITunesCategoryList implements TransferObject {
private static final long serialVersionUID = 3303125979016445238L;
#XmlElement
private List<ITunesCategory> categories = new ArrayList<ITunesCategory>();
...
}
However, regardless of which class I specify as my return type, I get the same error because the category identifier is the root element name of the JSON object.
Is there any way to tell JAXB/Jackson/JAX-RS/RESTeasy to ignore the root element name and just map the underlying object to Java? There is no way for me to know the root element name at develop/compile time, since it corresponds directly to the results returned by the search. Is there anything that can be done to get around this exception? Thanks for any help you can give!
I couldn't find much on dynamically ignoring the root, at least not anything that would be suitable in a JAX-RS environment. The only thing I could think is to write a custom deserializer, and just skip the root node. Something like
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
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 java.io.IOException;
import java.util.Map;
public abstract class IHateRootElemsJsonDeserializer<T> extends JsonDeserializer<T> {
private final ObjectMapper mapper = new ObjectMapper();
private final Class<T> cls;
public IHateRootElemsJsonDeserializer(Class<T> cls) {
this.cls = cls;
}
#Override
public T deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
JsonNode rootNode = jp.getCodec().readTree(jp);
Map.Entry<String,JsonNode> field = rootNode.fields().next();
JsonNode node = field.getValue();
T result = mapper.convertValue(node, cls);
return result;
}
}
Then just extend it with a concrete type.
public class GenreDeserializer extends IHateRootElemsJsonDeserializer<Genre>{
public GenreDeserializer() {
super(Genre.class);
}
}
Here's a test using the exact JSON you provided above
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
GenreDeserializer deserializer = new GenreDeserializer();
SimpleModule module = new SimpleModule();
module.addDeserializer(Genre.class, deserializer);
mapper.registerModule(module);
Genre genre = mapper.readValue(JSON_FILE, Genre.class);
System.out.println(genre);
genre = mapper.readValue(JSON_FILE, Genre.class);
System.out.println(genre);
}
static final File JSON_FILE = new File("json.json");
}
The model
public class Genre {
public String id;
public String name;
public String url;
public RssUrls rssUrls;
public ChartUrls chartUrls;
#Override
public String toString() {
return "Category{" + "id=" + id + ", name="
+ name + ", url=" + url + ", rssUrls=" + rssUrls + '}';
}
public static class RssUrls {
public String topVideoPodcastEpisodes;
public String topAudioPodcasts;
public String topVideoPodcasts;
public String topPodcasts;
public String topAudioPodcastEpisodes;
public String topPodcastEpisodes;
#Override
public String toString() {
return "RssUrls{" + "topVideoPodcastEpisodes=" + topVideoPodcastEpisodes
+ ", topAudioPodcasts=" + topAudioPodcasts
+ ", topVideoPodcasts=" + topVideoPodcasts
+ ", topPodcasts=" + topPodcasts
+ ", topAudioPodcastEpisodes=" + topAudioPodcastEpisodes
+ ", topPodcastEpisodes=" + topPodcastEpisodes + '}';
}
}
public static class ChartUrls {
public String videoPodcastEpisodes;
public String podcasts;
public String audioPodcastEpisodes;
public String audioPodcasts;
public String podcastEpisodes;
public String videoPodcasts;
#Override
public String toString() {
return "ChatUrls{" + "videoPodcastEpisodes=" + videoPodcastEpisodes
+ ", podcasts=" + podcasts
+ ", audioPodcastEpisodes=" + audioPodcastEpisodes
+ ", audioPodcasts=" + audioPodcasts
+ ", podcastEpisodes=" + podcastEpisodes
+ ", videoPodcasts=" + videoPodcasts + '}';
}
}
}
To configure the ObjectMapper in JAX-RS, you can have a look at this post

How to ignore attributes while serializing a class by ObjectMapper

I have a class with lots of attributes which are required for server side logic, but a few of those are required for UI. Now when I am creating a json from the class, all the attributes are written to json. I want to ignore some values only when it is converted to json. I Tried with #JsonIgnore. But it is not working.
My Class Is
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Student {
#JsonProperty("id")
protected Integer id;
#JsonProperty("name")
protected String name;
/**
* This field I want to ignore in json.
* Thus used #JsonIgnore in its getter
*/
#JsonProperty("securityCode")
protected String securityCode;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public String getSecurityCode() {
return securityCode;
}
public void setSecurityCode(String securityCode) {
this.securityCode = securityCode;
}
}
And I am writing this using
public static StringBuilder convertToJson(Object value){
StringBuilder stringValue = new StringBuilder();
ObjectMapper mapper = new ObjectMapper();
try {
stringValue.append(mapper.writeValueAsString(value));
} catch (JsonProcessingException e) {
logger.error("Error while converting to json>>",e);
}
return stringValue;
}
My Expected json should contain only :
id:1
name:abc
but what I am getting is
id:1
name:abc
securityCode:_gshb_90880..some_value.
What is wrong here, please help
Your #JsonProperty annotation overrides #JsonIgnore annotation. Remove #JsonProperty from securityCode and your desired json output will be produced.
If you want more advanced ignoring / filtering please take a look at:
#JsonView : http://wiki.fasterxml.com/JacksonJsonViews
#JsonFilter : http://wiki.fasterxml.com/JacksonFeatureJsonFilter

Adding a calculated field on jpa + jackson

How to add a calculated field to an entity?
DB table
table person {
id number,
first_name varchar,
last_name varchar,
...
}
Java entity
public class person {
BigDecimal id;
String firstName;
String lastName;
...//getters and setters
//what to add here???
public String getFullName() {
return firstName + " " + lastname;
}
}
I tried adding #Transient, but the field is ignored when converting to json.
I tried just leaving the method there, throws an exception that the setter is missing. adding the setter throws another exception that the field does not exist in the DB.
I tried adding #Transient and #JsonPropert, but the field is ignored when converting to json.
I tried adding #Formula, but hibernate (i think) says it is not implemented.
The idea is to have a simple calculated field that is ignored by jpa/hibernate but is used by jackson.
How can I accomplish this?
EDIT
Example full class
#Entity
#Table(name="FDF_PATIENT_COUNTIE")
#JsonIdentityInfo(generator = JSOGGenerator.class)
#JsonIgnoreProperties(ignoreUnknown = true)
#Audited
public class PatientCounty extends FgaBaseClass {
private static final long serialVersionUID = 1425318521043179798L;
private BigDecimal id;
private County FCounties;
private Patient patients;
public PatientCounty() {
}
public PatientCounty(County FCounties, Patient patients) {
this.FCounties = FCounties;
this.patients = patients;
}
#SequenceGenerator(name="generator", sequenceName="FDF_PATIENT_COUNTIE_SEQ")
#Id
#GeneratedValue(strategy=SEQUENCE, generator="generator")
#Column(name="ID", unique=true, nullable=false, precision=22, scale=0)
public BigDecimal getId() {
return this.id;
}
public void setId(BigDecimal id) {
this.id = id;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_F_COUNTIE")
public County getFCounties() {
return this.FCounties;
}
public void setFCounties(County FCounties) {
this.FCounties = FCounties;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_FDF_PATIENT")
public Patient getPatients() {
return this.patients;
}
public void setPatients(Patient patients) {
this.patients = patients;
}
}

Spring MVC + Jackson: field not being serialized

I am trying to make a simple round-trip with a REST API that leads to storing an entity into the db and then returns the stored entity.
Going down works fine and the entity is stored and correctly returned to the REST Controller. However, when I return it, Jackson seems to serialize it incorrectly, as the "name" attribute is not included.
This is the entity:
#Entity
#Configurable
public class MyEntity extends IdentifiableEntity {
private String name;
protected MyEntity() {
};
public MyEntity(String name) {
this.name = name;
}
}
and the extended entity:
#Configurable
#Inheritance(strategy = InheritanceType.JOINED)
#Entity
public abstract class IdentifiableEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version = 1;
public String toString() {
return ReflectionToStringBuilder.toString(this,
ToStringStyle.SHORT_PREFIX_STYLE);
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return this.version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
The REST controller is:
#RestController
#RequestMapping("/service")
public class Service {
#RequestMapping(value = "/public/{name}", method = RequestMethod.GET)
public MyEntity storeEntityPublic(#PathVariable String name) {
System.out.println("Hello " + name
+ ", I am saving on the db. (PUBLIC)");
MyEntity saved = controller.saveEntity(name);
return saved;
}
}
Then my business logic:
#Service
public class LogicController {
#Autowired
private MyEntityRepository myEntityRepository;
public MyEntity saveEntity(String name) {
MyEntity cg = new MyEntity(name);
return myEntityRepository.save(cg);
}
}
I am using Spring repositories:
#Repository
public interface MyEntityRepository extends JpaSpecificationExecutor<MyEntity>,
JpaRepository<MyEntity, Long> {
}
The returned JSON is:
{"id":12,"version":1}
Where is my "name" attribute? Is is set in the variable being returned by the REST controller.
I found the trick: MyEntity needs to have a public get for the property that has to be shown. A good reason to use a DTO pattern.
In response to your "I don't want to have my Entity "dirty"" comment: Jackson allows the use of so-called Mixins. They allow you to define annotations for your class outside the class itself. In your case it could look like this:
public abstract class MyEntityMixin {
#JsonProperty
private String name;
}
You may keep it as a field and annotate the field with #JsonProperty if you like.