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);
Related
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.
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'm wondering if there is any legit way to dynamically allocate name of JsonProperty so I would change it over time when needed ? With that being said I mean having :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record
{
public String Name;
#JsonIgnoreProperties(ignoreUnknown = true)
public static class QueryResult<T>
{
public List<T> records;
}
public static class QueryResultRecord extends QueryResult<Record>
{
}
}
Like above, I have a property Name, which by default will be named "Name" like this:
[
{
Name: "Test",
},
{
Name: "test",
},
]
Even though I have flexibility to use #JsonProperty("name") that's not a solution. What I am after is changing it multiple times when needed as I have some parameterized query which relies on it. So I would like to have Name, FirstName, LastName and so on. Is refletion api the right thing to use it here ?
The easiest legit way is to write custom AnnotationIntrospector:
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class MyJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector
{
private static final long serialVersionUID = 1L;
#Override
public PropertyName findNameForSerialization(Annotated a) {
PropertyName pn = super.findNameForSerialization(a);
if (pn.getSimpleName().equals("Name")) {
return pn.withSimpleName("LastName"); // set property name to your heart's content...
}
return pn;
}
}
and then pass it to the jackson mapper:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new MyJacksonAnnotationIntrospector());
Record r1 = new Record();
mapper.writeValue(System.out, r1);
Note: the same introspector is used during deserialization.
I didn't found any simple way to do it but you can use a custom JsonSerializer and implement your logic in it :
// Record class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record {
protected String name;
public Record(String name) {
this.name = Name;
}
// ...
}
// RecordJsonSerializer class
public static class RecordJsonSerializer extends JsonSerializer<Record> {
private static final String[] NAMES = new String[]{
"Name",
"FirstName"
// ...
};
protected int idx;
public RecordJsonSerializer() {
idx = 0;
}
#Override
public void serialize(Record r, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException {
jg.writeStartObject();
jg.writeObjectField(NAMES[idx++], r.name); // Change the field name
jg.writeEndObject();
}
}
// Use case
Record[] records = new Record[]{
new Record("r0"),
new Record("r1")
};
ObjectMapper mapper = new ObjectMapper()
.registerModule(
new SimpleModule("Record")
.addSerializer(Record.class, new RecordJsonSerializer())); // Register the serializer instance
System.out.println(mapper.writeValueAsString(records));
The output of this is: [{"Name":"r0"},{"FirstName":"r1"}]
Of course you must change the logic to define the property name to use when serializing the object (mine will crash with 3 records but it's just a simple example).
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.