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.
CodePudding user response:
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 mapdeliveryModes
).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
} ]
}
CodePudding user response:
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