I get a JSON string that I convert to an object. One of the property within the JSON sometimes would be null. If the property is null, I want the default value to be set to 0.
This is my class:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Car {
#JsonProperty(value = "car_name")
private String carName;
#JsonProperty(value = "car_value")
private Long carValue;
#JsonProperty(value = "Specifications")
private Map<String, String> Specifications;
}
I use object mapper to convert the JSON string to the object
public List<Car> stringToCar(String json) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
return om.readValue(json, new TypeReference<List<Car>>() {} );
}
carValue would sometimes have null value, if that happens I want it be set as 0. Is it possible to do in a efficient way rather than looping through the object and manually setting the value to 0
You have multiple ways to do this.
1) Setter that receives long instead
This is actually not straightforward but it works. If you define the setter as follows it will do what you need:
public void setCarValue(long carValue) {
this.carValue = carValue;
}
However, this feels like a hack to me, so I would not suggest you use it.
2) Custom deserializer
This one is more complex but also much easier to understand and explicit about your intent.
public class CustomLongDeserializer extends JsonDeserializer<Long> {
#Override
public Long deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = jp.getText();
if (text == null || text.isEmpty()) {
return 0;
} else {
return Long.valueOf(text);
}
}
}
Then you could apply the serializer on the attribute as follows:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Car {
#JsonProperty(value = "car_name")
private String carName;
#JsonProperty(value = "car_value")
#JsonDeserialize(using = CustomLongDeserializer.class)
private Long carValue;
#JsonProperty(value = "Specifications")
private Map<String, String> Specifications;
}
Or apply it as a global deserializer to be used to deserialize every single Long:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Long.class, new CustomLongDeserializer());
mapper.registerModule(module);
It's not a generic solution, but in your specific use case you could use the long primitive instead of Long which will coerce null to 0 when deserialized.
If you don't want to create a separate class like #João Dias mentioned, you can define the constructor yourself instead of using #AllArgsConstructor, something like :
public Car(String carName,
Long carValue,
Map<String, String> Specifications){
this.carName = carName;
this.Specifications = Specifications;
this.carValue = (carValue == null ? 0 : carValue);
}
The same can be used for #Builder annotation like :
builder().carValue(value == null ? 0 : value).build();
Again, if you have more variables to handle you should consider previous response.
Related
if I have the following yaml (which I found online) representing a java Order class, order.yaml:
orderNo: A001
customerName: Customer, Joe
orderLines:
- item: No. 9 Sprockets
quantity: 12
unitPrice: 1.23
- item: Widget (10mm)
quantity: 4
unitPrice: 3.45
I was able to use
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
Order order = objectMapper.readValue(new File(<path_to_order>), Order.class);
But this means that I need to define orderNo and orderLines in advance... If I have a giant yaml with a bunch of nested properties this can get really annoying. What if I want a class than can read one property or a class that can read another property and "ignore" other ones? Is that even possible? That way I could just specify which java object I want without necessarily having to recursively define every property of the yaml. Thank you!
The Map approach will lose you the type safety. There's no need to define every single property. You can use the Json annotations just fine with YAML too, it's just a historical leftover that it is called Json. What you are looking for is #JsonIgnoreProperties(ignoreUnknown = true).
If you don't like to specify the Annotation for every class, use objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);.
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
Order order = objectMapper.readValue(new File("foo.yml"), Order.class);
System.out.println(order.getOrderLines().get(0).getItem());
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class Order {
private String orderNo;
private List<OrderLine> orderLines;
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public List<OrderLine> getOrderLines() {
return orderLines;
}
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class OrderLine {
private String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}
}
You can read the json in a Map and then retrieve whatever you want from there
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
Map<String,Object> jsonMap = objectMapper.readValue(new File(<path_to_order>), Map.class);
Having 2 simple classes like:
#Setter
#Getter
public class Continent {
private String id;
private String code;
private String name;
}
#Setter
#Getter
public class Country {
private String id;
private String alpha2Code;
private String alpha3Code;
private String name;
private Continent continent;
}
when reading the following yaml:
id: brazil
alpha2_code: BR
alpha3_code: BRA
name: Brazil
continent_id: south-america
I would like to use the continent_id to retrieve the Continent from a application scoped List<Continent>.
The best thing I could think of is using a custom Deserializer like:
public class CountryDeserializer extends StdDeserializer<Country> {
public CountryDeserializer() {
super(Country.class);
}
#Override
public Country deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// This works... the `continentId` is retrieved!
JsonNode node = jp.getCodec().readTree(jp);
String continentId = node.get("continent_id").asText();
// How to access application scoped continents? Use injectable value?
Continent continent = getContinent(continentId);
// Read value for other properties; don't want to read other properties manually!
Country country = jp.getCodec().readValue(jp, Country.class);
// But unfortunately this throws a StackOverflow...
country.setContinent(continent);
return country;
}
}
But the problem is I would like Jackson to automatically read the other properties.
I don't want to this manually as if in the future a property is added it might be forgotten, and with other entities with 20 properties this becomes very cumbersome...
I tried with Country country = jp.getCodec().readValue(jp, Country.class); but that gives stack overflow exception as it gets in a loop with the custom deserializer obviously.
Is there a way to solve this using Jackson, or is there another better approach to get and set the Continent in this scenario?
Note I'm working with a pre-defined set of domain classes I cannot change.
I can modify the object mapper and add mixins if needed.
Instead of using a CountryDeserializer I've implemented it using a ContinentReferenceDeserializer.
This way the other Country properties are deserialized "automatically".
It looks like:
public class ContinentReferenceDeserializer extends StdDeserializer<Continent> {
public ContinentReferenceDeserializer() {
super(Continent.class);
}
#Override
public Continent deserialize(JsonParser parser, DeserializationContext context) throws IOException {
String id = parser.getText(); // returns the continent id (`continent_id` in json)
Map<String, Continent> continents = (Map<String, Continent>) context.findInjectableValue("continents", null, null);
return continents.gett(id);
}
}
and it is used in the CountryMixIn like:
public abstract class CountryMixIn {
#JsonProperty("continent_id")
#JsonDeserialize(using = ContinentReferenceDeserializer.class)
abstract Continent getContinent();
}
Note that if you don't use Mix-ins but directly annotate domain/dtoa classes, above can be applied to these as well instead.
The ObjectMapper can be setup then like:
Map<String, Continent> continents = .. // get the continents
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Country.class, CountryMixIn.class);
mapper.setInjectableValues(new InjectableValues.Std().addValue("continents", continents));
and then can be called like:
String json = .. // get the json
Country country = mapper.readValue(json, Country.class);
I have the below json, where the body key contains a value which is a string representation of a JSON object, how do I convert it to a Java Object ?
I can extract the body value by converting the JSON to a Map, but I don't know how I should proceed from there
input.json file
{
"body": "{\n\t\"username\": \"TestUser\",\n\t\"password\": \"TestPassword\"\n}"
}
The User POJO is as below,
class User {
private String username;
private String password;
... getters, setters and no-arg constructor
}
My code looks something like this, I need to implement convertToUser function
public static void main(String[] args) {
String jsonContent = readJsonFile("input.json");
String escapedJsonBody = getBody(s);
User user = convertToUser(escapedJsonBody, User.class);
}
I am already using jackson java library, any insights on doing this with jackson is highly appreciated.
One way to do it is to create DTOs and converter. Having DTOs like (i have nested the class declarations jsut to save space in answer):
#Getter #Setter
public class Input { // this level maps to the whole input.json
#JsonDeserialize(using = BodyDeserializer.class) // custom deserializer below
private Body body; // this is the body-attribute in JSON
#Getter #Setter
public static class Body {
private User user;
#Getter #Setter
public static class User {
private String username;
private String password;
}
}
}
the converter:
public class BodyDeserializer extends JsonDeserializer<Body> {
private ObjectMapper om = new ObjectMapper(); // this is to read the user from string
#Override
public Body deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String embedded = p.readValueAs(String.class);
Body body = new Body();
body.setUser(om.readValue(embedded, User.class)); // here is the trick
return body;
}
}
Use like:
ObjectMapper om = new ObjectMapper();
String input = "{\"body\": \"{\\n\\t\\\"username\\\": \\\"TestUser\\\",\\n\\t\\\"password\\\": \\\"TestPassword\\\"\\n}\"}";
Input r = om.readValue(input, Input.class);
This way the conversion happens in generic way only con might be that you do not like to create DTOs and dig the user like Input.getBody().getUser();
To convert a JSON String to a java pojo you can use Jackson's ObjectMapper class that will assist you to do this.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(inputJson, User.class);
More info can be found on Jackson's github page
I have two classes Athlete and Injury, the last one contains Athlete object, when the serialization happens I get the following JSON representation back:
{"id":X,"kindOfInjury":"...","muscle":"...","side":"...","outOfTrainig":Y,"injuryDate":"2018-Jun-02","athlete":{"id":X,"firstName":"...","lastName":"...","age":X,"email":"..."}}
I don't want to get all the information about Athlete - just an id value, like "athleteId":1, instead of getting the entire object representation.
So, I have found that I need to apply my custom Serializer which implements StdSerializer on Injury class. So this is what I got so far:
class InjurySerializer extends StdSerializer<Injury> {
public InjurySerializer() {
this(null);
}
public InjurySerializer(Class<Injury> i) {
super(i);
}
#Override
public void serialize(
Injury value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.getId());
jgen.writeStringField("kindOfInjury", value.getKindOfInjury());
jgen.writeStringField("muscle", value.getMuscle());
jgen.writeStringField("side", value.getSide());
jgen.writeNumberField("outOfTraining", value.getOutOfTraining());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MMM-dd");
Date date = new Date();
String ourformat = formatter.format(date.getTime());
jgen.writeStringField("injuryDate", ourformat);
jgen.writeNumberField("athleteId", value.getAthlete().getId());
jgen.writeEndObject();
}
}
And the actual Injury class:
#Entity
#Table(name = "INJURY")
#JsonSerialize(using = InjurySerializer.class)
public class Injury {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "INJURY_ID")
private Long id;
#Column(name = "KIND_OF_INJURY")
private String kindOfInjury;
#Column(name = "MUSCLE")
private String muscle;
#Column(name = "SIDE")
private String side;
#Column(name = "OUT_OF_TRAINING")
private Integer outOfTraining;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MMM-dd")
#Column(name = "INJURY_DATE")
private Date injuryDate;
#ManyToOne
#JoinColumn(name = "ATHLETE_ID")
private Athlete athlete;
So, this solution works, but it looks terrible...
Question is the following:
1) Is there any mechanism which provides me functionality to change the serialization of only ONE property which I really need, instead of writing all this tedious code, where the actual change is only in this line? :
jgen.writeNumberField("athleteId", value.getAthlete().getId());
2) Could you recommend me something to read about Jackson because at this point I have a little bit mess in my head about it?
Thanks for the patience and I'm looking forwards for your responses :)
You can use the Data Transfer Object (DTO) for that purposes.
Create a simple POJO like this:
public class InjuryDTO {
//all other required fields from Injury model...
#JsonProperty("athlete_id")
private Long athleteId;
}
And converter for it:
#Component
public class InjuryToDTOConverter{
public InjuryDTO convert(Injury source){
InjuryDTO target = new InjuryDTO();
BeanUtils.copyProperties(source, target); //it will copy fields with the same names
target.setAthleteId(source.getAthlete().getId());
return target;
}
}
You can use it like that:
#RestController("/injuries")
public class InjuryController {
#Autowired
private InjuryToDTOConverter converter;
#Autowired
private InjuryService injuryService;
#GetMapping
public InjuryDTO getInjury(){
Injury injury = injuryService.getInjury();
return converter.convert(injury);
}
}
The benefit of this approach is that you can have multiple DTOs for different purposes.
You might find it less tedious to use the #JsonIgnore annotation instead of writing a custom serializer. Take this example
public class Person {
private int id;
#JsonIgnore
private String first;
#JsonIgnore
private String last;
#JsonIgnore
private int age;
// getters and setters omitted
}
When Jackson serializes this class, it only includes the "id" property in the resulting JSON.
#Test
void serialize_only_includes_id() throws JsonProcessingException {
final var person = new Person();
person.setId(1);
person.setFirst("John");
person.setLast("Smith");
person.setAge(22);
final var mapper = new ObjectMapper();
final var json = mapper.writeValueAsString(person);
assertEquals("{\"id\":1}", json);
}
You can try manupulating json string using basic string replace method.
I ran your json and converted it to your desired format:
public static void main(String args[]) {
String json = "{\"id\":123,\"kindOfInjury\":\"...\",\"muscle\":\"...\",\"side\":\"...\",\"outOfTrainig\":Y,\"injuryDate\":\"2018-Jun-02\",\"athlete\":{\"id\":456,\"firstName\":\"...\",\"lastName\":\"...\",\"age\":14,\"email\":\"...\"}}";
JsonObject injury = new JsonParser().parse(json).getAsJsonObject();
JsonObject athelete = new JsonParser().parse(injury.get("athlete").toString()).getAsJsonObject();
String updateJson = injury.toString().replace(injury.get("athlete").toString(), athelete.get("id").toString());
updateJson = updateJson.replace("athlete", "athleteId");
System.out.println(updateJson);
}
output:
{"id":123,"kindOfInjury":"...","muscle":"...","side":"...","outOfTrainig":"Y","injuryDate":"2018-Jun-02","athleteId":456}
Dependency:
implementation 'com.google.code.gson:gson:2.8.5'
If you can replace with regex that will be bit more cleaner.
I have a piece of JSON, that looks like this:
{
"authors": {
"author": [
{
"given-name": "Adrienne H.",
"surname": "Kovacs"
},
{
"given-name": "Philip",
"surname": "Moons"
}
]
}
}
I have created a class to store Author information:
public class Author {
#JsonProperty("given-name")
public String givenName;
public String surname;
}
And two wrapper classes:
public class Authors {
public List<Author> author;
}
public class Response {
public Authors authors;
}
This is working, but having two wrapper classes seems to be unnecessary. I want to find a way to remove Authors class and have a list as a property of Entry class. Is something like that is possible with Jackson?
Update
Solved that with custom deserializer:
public class AuthorArrayDeserializer extends JsonDeserializer<List<Author>> {
private static final String AUTHOR = "author";
private static final ObjectMapper mapper = new ObjectMapper();
private static final CollectionType collectionType =
TypeFactory
.defaultInstance()
.constructCollectionType(List.class, Author.class);
#Override
public List<Author> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
ObjectNode objectNode = mapper.readTree(jsonParser);
JsonNode nodeAuthors = objectNode.get(AUTHOR);
if (null == nodeAuthors // if no author node could be found
|| !nodeAuthors.isArray() // or author node is not an array
|| !nodeAuthors.elements().hasNext()) // or author node doesn't contain any authors
return null;
return mapper.reader(collectionType).readValue(nodeAuthors);
}
}
And using it like this:
#JsonDeserialize(using = AuthorArrayDeserializer.class)
public void setAuthors(List<Author> authors) {
this.authors = authors;
}
Thanks #wassgren for the idea.
I see at least two approaches to do this if you want to get rid of wrapper classes. The first is to use the Jackson Tree Model (JsonNode) and the second is to use a deserialization feature called UNWRAP_ROOT_VALUE.
Alternative 1: Use JsonNode
When deserializing JSON using Jackson there are multiple ways to control what type of objects that are to be created. The ObjectMapper can deserialize the JSON to e.g. a Map, JsonNode (via the readTree-method) or a POJO.
If you combine the readTree-method with the POJO conversion the wrappers can be completely removed. Example:
// The author class (a bit cleaned up)
public class Author {
private final String givenName;
private final String surname;
#JsonCreator
public Author(
#JsonProperty("given-name") final String givenName,
#JsonProperty("surname") final String surname) {
this.givenName = givenName;
this.surname = surname;
}
public String getGivenName() {
return givenName;
}
public String getSurname() {
return surname;
}
}
The deserialization can then look something like this:
// The JSON
final String json = "{\"authors\":{\"author\":[{\"given-name\":\"AdrienneH.\",\"surname\":\"Kovacs\"},{\"given-name\":\"Philip\",\"surname\":\"Moons\"}]}}";
ObjectMapper mapper = new ObjectMapper();
// Read the response as a tree model
final JsonNode response = mapper.readTree(json).path("authors").path("author");
// Create the collection type (since it is a collection of Authors)
final CollectionType collectionType =
TypeFactory
.defaultInstance()
.constructCollectionType(List.class, Author.class);
// Convert the tree model to the collection (of Author-objects)
List<Author> authors = mapper.reader(collectionType).readValue(response);
// Now the authors-list is ready to use...
If you use this Tree Model-approach the wrapper classes can be completely removed.
Alternative 2: remove one of the wrappers and unwrap the root value
The second approach is to remove only one of the wrappers. Assume that you remove the Authors class but keep the Response-wrapper. If you add the a #JsonRootName-annotation you can later unwrap the top-level name.
#JsonRootName("authors") // This is new compared to your example
public class Response {
private final List<Author> authors;
#JsonCreator
public Response(#JsonProperty("author") final List<Author> authors) {
this.authors = authors;
}
#JsonProperty("author")
public List<Author> getAuthors() {
return authors;
}
}
Then, for your mapper simply use:
ObjectMapper mapper = new ObjectMapper();
// Unwrap the root value i.e. the "authors"
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
final Response responsePojo = mapper.readValue(json, Response.class);
The second approach only removes one of the wrapper classes but instead the parsing function is quite pretty.