Injecting additional fields in serialization - java

I have objects which I serialize using Jackson. e.g.
class A {
int id;
String value
}
is serialized to:
{
"id": 1,
"value": "test"
}
Now I want an additional field in the serialized version:
{
"id": 1,
"value": "test"
"__errors__": [
{ field: "id", "message": "already in use" },
{ field: "value", "message": "field to long" },
]
}
This field is composed by a list of class Error:
class Error {
String field;
String message;
}
This should be done while serializing and work without adding a method / property to the class A.
So is there a way to inject additional fields into jackson mapper? Best would be a generic method which would work for all classes passed to the mapper without additional code.
(Background: I want to use this to return validation errors to AngularJS within the normal result. Because putting both in a container would complicate the code on the client side.)
Thanks in advance!
EDIT:
I imagine the final usage could look something like this:
ObjectMapper mapper = ...;
PrintWriter out = ...;
// Normally the parsed user input
A a = new A( 5, "test" );
// Normally generated by validation of user input
List<Error> errors = Arrays.asList( new Error( "id", "already in use" ) );
// No write it as combined json
mapper.writeValue( out, a, error );
// ... or ...
customWrite( out, a, error );
EDIT2:
Currently I'm looking at two possible solutions:
Write a custom Serializer. Seems like a clean solution but needs to do all the stuff the default serializer does. But is a complex solution because I first have to figure out how custom serialization and extending the default works.
Just use String replacement and concatenation. Write both objects seperately and remove staring/closing curly braces and append one on another. Quick, but very dirty. (I really don't like this but it could work.)

You can easily do that using ObjectMapper.convertValue method. See below example:
A a = new A(101, "test");
List<Error> errors = Arrays.asList(new Error("id", "already in use"), new Error("value",
"field to long"));
// Create Jackson objects
ObjectMapper jsonMapper = new ObjectMapper();
MapType mapType = jsonMapper.getTypeFactory().constructMapType(LinkedHashMap.class,
String.class, Object.class);
// Create map
LinkedHashMap<String, Object> map = jsonMapper.convertValue(a, mapType);
map.put("__errors__", errors);
// Serialize map
String json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);
System.out.println(json);
Above example prints:
{
"id" : 101,
"value" : "test",
"__errors__" : [ {
"field" : "id",
"message" : "already in use"
}, {
"field" : "value",
"message" : "field to long"
} ]
}

Related

Using Jackson to extract information from Property names

I currently receive the following JSON body
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"shippingPrice[zone=BE,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=BE,method=STD]": 1,
"deliveryTimeLatestDays[zone=BE,method=STD]": 1,
"shippingPrice[zone=NL,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=NL,method=STD]": 1,
"deliveryTimeLatestDays[zone=NL,method=STD]": 1
}
As you can see, I have similar properties that differ by zone and method enclosed in square brackets. I don't want to change the code every time a new zone and/or method is introduced. I'm looking for a more dynamic way you deserialize this via Jackson.
Is there a way to automatically deserialize all properties starting with shippingPrice, deliveryTimeEarliestDays and deliveryTimeLatestDays into the following format?
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"deliveryModes":[
{
"method":"STD"
"zone":"BE",
"shippingPrice":0.0,
"deliveryTimeEarliestDays":1,
"deliveryTimeLatestDays":1
},{
"method":"STD"
"zone":"NL",
"shippingPrice":0.0,
"deliveryTimeEarliestDays":1,
"deliveryTimeLatestDays":1
}]
}
My first idea was to use the #JsonAnySetter annotation and put everything in a Map but that still leaves me with manual parsing of the field name.
My Second Idea was to build a custom deserializer where I loop over all attributes and filter out all the ones that start with shippingPrice, deliveryTimeEarliestDays and deliveryTimeLatestDays and map them to the described format above.
In order to achieve the required result, you need to implement deserialization logic yourself, it can't be done only by sprinkling a couple of data binding annotations.
That's how it can be done.
Assume here's a POJO that corresponds to your input JSON (to avoid boilerplate code, I'll use Lombok annotations):
#Getter
#Setter
public static class MyPojo {
private String productId;
private String offerId;
private String format;
private String sellerId;
private String sellerName;
#JsonIgnore // we don't want to expose this field to Jackson as is
private Map<DeliveryZoneMethod, DeliveryMode> deliveryModes = new HashMap<>();
#JsonAnySetter
public void setDeliveryModes(String property, String value) {
DeliveryZoneMethod zoneMethod = DeliveryZoneMethod.parse(property);
DeliveryMode mode = deliveryModes.computeIfAbsent(zoneMethod, DeliveryMode::new);
String name = property.substring(0, property.indexOf('['));
switch (name) {
case "shippingPrice" -> mode.setShippingPrice(new BigDecimal(value));
case "deliveryTimeEarliestDays" -> mode.setDeliveryTimeEarliestDays(Integer.parseInt(value));
case "deliveryTimeLatestDays" -> mode.setDeliveryTimeLatestDays(Integer.parseInt(value));
}
}
public Collection<DeliveryMode> getModes() {
return deliveryModes.values();
}
}
Properties productId, offerId, format, sellerId, sellerName would be parsed by Jackson in a regular way.
And all other properties formatted like "shippingPrice[zone=BE,method=STD]" would be handled by the method annotated with #JsonAnySetter.
To facilitate extracting and storing information from such properties I've defined a couple of auxiliary classes:
DeliveryZoneMethod which contains information about a zone and delivery method as its name suggests (the purpose of this class is to serve as Key in the map deliveryModes).
DeliveryMode which is meant to contain all the need information that correspond to a particular zone and method of delivery.
For conciseness, DeliveryZoneMethod can be implemented as a Java 16 record:
public record DeliveryZoneMethod(String method, String zone) {
public static Pattern ZONE_METHOD = Pattern.compile(".+zone=(\\p{Alpha}+).*method=(\\p{Alpha}+)");
public static DeliveryZoneMethod parse(String str) {
// "shippingPrice[zone=BE,method=STD]" - assuming the given string has always the same format
Matcher m = ZONE_METHOD.matcher(str);
if (!m.find()) throw new IllegalArgumentException("Unable to parse: " + str);
return new DeliveryZoneMethod(m.group(1), m.group(2));
}
}
And here's how DeliveryMode might look like:
#Getter
#Setter
public static class DeliveryMode {
private String method;
private String zone;
private BigDecimal shippingPrice;
private int deliveryTimeEarliestDays;
private int deliveryTimeLatestDays;
public DeliveryMode(DeliveryZoneMethod zoneMethod) {
this.method = zoneMethod.method();
this.zone = zoneMethod.zone();
}
}
Usage example:
public static void main(String[] args) throws JsonProcessingException {
String json = """
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"shippingPrice[zone=BE,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=BE,method=STD]": 1,
"deliveryTimeLatestDays[zone=BE,method=STD]": 1,
"shippingPrice[zone=NL,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=NL,method=STD]": 1,
"deliveryTimeLatestDays[zone=NL,method=STD]": 1
}
""";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json, MyPojo.class);
String serializedJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(myPojo);
System.out.println(serializedJson);
}
Output:
{
"productId" : "90000011",
"offerId" : "String",
"format" : "String",
"sellerId" : "String",
"sellerName" : "String",
"modes" : [ {
"method" : "BE",
"zone" : "STD",
"shippingPrice" : 0.0,
"deliveryTimeEarliestDays" : 1,
"deliveryTimeLatestDays" : 1
}, {
"method" : "NL",
"zone" : "STD",
"shippingPrice" : 0.0,
"deliveryTimeEarliestDays" : 1,
"deliveryTimeLatestDays" : 1
} ]
}
I would go with your first idea to deserialize your JSON into a map. And yes you will still need to analyze the map keys. It is easy to deserialize Json into a map with Json Jackson, but there is an Open source library called MgntUtils that provides class JsonUtils which is a thin wrapper over Json-Jackson library. using it you can very simply deserialize your Json into a Map (or any other class). Your code would look like this:
try {
Map<String, Object> map = JsonUtils.readObjectFromJsonString(jsonStr, Map.class);
System.out.println(map);
} catch (IOException e) {
...
}
Here is Javadoc for JsonUtils. The library can be obtained as maven artifact or on Github (with source code and Javadoc).
Disclaimer: This library is written and maintained by me

Handling empty JSON

I need to send following JSON in API BODY POST request:
{
"name": "",
"type": "TEMP",
"shared": false,
"search": {
},
"order": [
]
}
In my MainBody.java, declared
private String name;
private String type;
private boolean shared;
private JSON search;
private Object order;
and defined getters and setters.
In Payload.java,
MainBody mb = new MainBody();
mb.setName("");
mb.setType("TEMP");
mb.setShared(false);
mb.setSearch(null);
mb.setOrder(new ArrayList<>());
ObjectMapper om = new ObjectMapper();
String myData = om.writerWithDefaultPrettyPrinter().writeValueAsString(mb);
System.out.println(myData);
results
{
"name" : "",
"type" : "TEMP",
"shared" : false,
"search" : null,
"order" : [ ]
}
Please assist with how search as { } can be achieved as per expected JSON instead of null.
TIA.
Instead of setting search to null, you need to set it to an empty object. I'm not sure which JSON library you are using, but there should be an object constructor like new JsonObject(). Depending on what the allowed values for search are, you may also want to consider representing it in your class as Map<String, String> or something like that.
I would try something like this:
mb.setSearch(new JSON());
This way you create empty object and there should be only {}. It also depends on which JSON library do you use.
Issue resolved after using JSONMapper.

Parse JSON with JsonArray and HashMap

I need to parse the following JSON and add values from it into three different Java objects. I was thinking to form other 3 Jsons in order to do this. I have some issues with parsing, as the JSON is a little bit complicated. The JSON is below:
{
"totalCount": 1,
"results": [
{
"teleCommunications": [
{
"areaCode": "100",
"telephoneNumber": "300-2444",
"internationalAreaCode": "",
"communicationType": 1
},
{
"areaCode": "100",
"telephoneNumber": "200-2555",
"internationalAreaCode": "",
"communicationType": 5
}
],
"delegate": {
"id": 0,
"range": 0,
},
"name": "Andrew",
"composedKey": {
"id": 615,
"range": 50,
},
"isBranchAddress": false,
"emailAddresses": [
{
"emailAddressType": 9,
"emailAddress": "andrew.brown#gmail.com"
}
],
"name": "Brown",
"zipCodeCity": "65760 Leipzig",
"salutation": "Mr.",
"openingDate": "2019-09-20",
"streetHouseNumber": "Offenbach. 37",
"modificationTimestamp": "2018-01-27"
}
]
}
I need to get separately the values from name, zipCodeCity, salutation, openingDate, streetHouseNumber in a JSON ( or any other way) , emailAddresses in a different JSON and the other data in another one.
I tried with this piece of code:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
HashMap<String, Object> result = new ObjectMapper().readValue(response, HashMap.class);
Object object = result.get("results");
List<HashMap<String, String>> jsonValues = (List<HashMap<String, String>>)object;
for(String key : jsonValues.get(0).keySet()){
System.out.println("name value " + jsonValues.get(0).get("name"));
System.out.println("zip code City " + jsonValues.get(0).get("zipCodeCity"));
}
The problem is that I would not go for such a hardcoded way...it is not very suitable.
Does anyone know a better approach for storing the values I need and parsing the Json more optimally?
Thank you
Look for this link, there's a demo application specially for you: https://github.com/nalmelune/jackson-demo-58591850 (look at JacksonTest)
First of all, you'll need data-classes for this (or so called dto), representing structure. You can use online generators for that (google "json to java dto online") or do it yourself. For example, here's root object (see github link for more):
public class Root {
private int totalCount;
private List<Results> results;
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getTotalCount() {
return this.totalCount;
}
public void setResults(List<Results> results) {
this.results = results;
}
public List<Results> getResults() {
return this.results;
}
}
Then configure your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
And use it (don't create new mapper, as it is in your example):
Root result = mapper.readValue(response, Root.class);
Finally, you can access it as usual Plain Old Java Objects:
for (Results resultItem : result.getResults()) {
System.out.println(resultItem.getSalutation());
System.out.println(resultItem.getName());
System.out.println(resultItem.getZipCodeCity());
System.out.println(resultItem.getOpeningDate());
System.out.println(resultItem.getStreetHouseNumber());
}
To make it work be sure validate your json (there were invalid commas after "range", and "name" comes twice and so on, it fixed on github). And be sure to include jsr310 in classpath by adding com.fasterxml.jackson.datatype:jackson-datatype-jsr310 module. You will need it for LocalDate and LocalDateTime objects.
You may create java class something like below
#Data
class MyCustomClass{
int totalCount;
List<TeleCommunicationDetail> results;
// other props
}
Next you can add attributes to TeleCommunicationDetail and finally you can use MyCustomClass reaonseInJavaObject = objectMapper.readValue(myJson,MyCustomClass.class);
Refer to this question for details.
Your approach is correct, as you simply read your JSON object as Map<String, Object> which always will work (as long as JSON Object is valid). And then you retrieve your values that you expect to be there. Another approach is to create a Class that maps to your original JSON and parse JSON with ObjectMapper into your class. Then you can retrieve your data with your class setters and getters. In this case, you don't need to use key Strings like "results" etc but you have to ensure that your JSON is not only valid JSON but always conforms to your class. Pick your option...

When parsing nested JSON using Jackson, which method is the preferred approach: ArrayNode or TypeReference?

I have to parse the following JSON. I have two solutions and I'm trying to decide which is the preferred approach.
{
"created": true,
"entries": [{
"NewValue": "test1",
"Operation": "ADD",
"FieldIdentifier": "name",
"FieldType": "AN",
"FieldLength": 35,
"OriginalValue": ""
},
{
"NewValue": "test2",
"Operation": "ADD",
"FieldIdentifier": "age",
"FieldType": "AN",
"FieldLength": 35,
"OriginalValue": "testOrig"
}]
}
Solution 1: Use readTree to get the JSON into a JsonNode object we can query, then convert into a list
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonObject = mapper.readTree(form.getFieldList());
ArrayNode fieldListArray = (ArrayNode) jsonObject.get("fieldList");
if (fieldList != null) {
FieldDefType[] listOfFieldDefs = mapper.convertValue(fieldList, FieldDefType[].class);
if (listOfFieldDefs != null) {
request.setFieldList(Arrays.asList(listOfFieldDefs));
}
}
Solution 2: Use TypeReference to map from JSON to POJO object and then retrieve my fields from there.
FieldListModel fieldListModel = new FieldListModel();
ObjectMapper mapper = new ObjectMapper();
fieldListModel = mapper.readValue(form.getFieldList(), new FieldListTypeReference());
request.setFieldList(fieldListModel.getEntries());
// At bottom of this class I have my named static inner class - Sonar scan requested this
static class FieldListTypeReference extends TypeReference<FieldListModel> {}
I'm leaning towards solution 1 as there is no need for a named static inner class and anecdotally I have heard on Stack Overflow that TypeReference is slow. The only issues with solution 1 I can think of is the need for more null checks and more lines of code.

Parse json string for specific value using JSONObject

In java, I am trying to parse values from this json..
[
{
"2012-01-02": {
"age": 3,
"dob": "2010-01-03",
"name": "jack"
},
"2012-01-03": {
"age": 3,
"dob": "2010-01-04",
"name": "jill"
},
"2012-01-04": {
"age": 3,
"dob": "2010-01-05",
"name": "john"
},
"2012-01-05": {
"age": 3,
"dob": "2010-01-06",
"name": "miran"
}
}
]
Using JSONObject, I was trying to get the value of just "age" and then add them up to do some data manipulation.
I created a JSONObject
Created an iterator and then stored them to a map
This gets me the inner element like:
{
"age": 3,
"dob": "2010-01-06",
"name": "miran"
}
After this, not sure how to extract just age from each element. Do i create another jsonobject and pass this new string, extract age out of it or is there a better way to do this? (I am sure there is one)
UPDATE:
This is what I currently have that gives me {"age":3,"dob":"2012-01-06","name":"miran"}
JSONObject jsonobj = new JSONObject();
try {
jsonobj = new JSONObject(pastweekVol);
Iterator iter = jsonobj.keys();
Map<String, String> map = new HashMap<String, String>();
while(iter.hasNext()){
String jsonkey = (String)iter.next();
String value = jsonobj.getString(jsonkey);
logger.debug("first pass value is: {}", value);
} catch (JSONException je) {
logger.debug("exception is: {}",je);
}
I was thinking that since I am getting {"age":3,"dob":"2012-01-06","name":"miran"}, I would create another json object and pass in this string, which will give me value of "age". The problem here is that I get repetitive values. Of course, something very basic is missing here but I can't seem to figure that out.
If you have the inner element as a JSONObject instance - say person - then you can directly access the age:
int age = person.getInt("age");
and do something with it:
sum += age;
You might consider a library like Google's GSON (http://code.google.com/p/google-gson/) if you want to be able to easily parse arbitrarily complex JSON strnigs into generic objects.
Using org.json is probably not your best bet -- this API has many flaws. Using Jackson, you can easily extract age from each member value:
ObjectMapper mapper = new ObjectMapper();
JsonNode fullDocument = mapper.readTree(xxx); // xxx can be many things
// Not an object? Bail out
if (!fullDocument.isObject())
throw new IllegalArgumentException("not an object");
// This will iterate through object values
for (JsonNode value: fullDocument)
// do something with value.get("age")
// in particular, you can test for .isIntegralNumber()

Categories

Resources