Jackson Serialize overloaded output - java

I have an API which is being extended to be consumed by another system. Pre-req's exist whereby each frontend has different expectations for one json field.
Example:
The response field 'amount' must be either a String or an int, depending on which value I receive from downstream. So for some instances I will return a string value in the json, while in others I will return int.
Expected json outputs:
{
"amount": 21
}
or
{
"amount": "21"
}
I have done the following:
class Response {
#JsonProperty("amount")
private int amount;
#JsonProperty("amount")
private String amountString;
// Getters and setters
Hoping that I would be able to return either an int or String value for the json field 'amount' but I'm getting the following error:
com.fasterxml.jackson.databind.JsonMappingException: Conflicting getter definitions for property "amount"
Any help would be appreciated

You can not do that. The element in your JSON is called amount, you can not bind it to 2 different variables.
You are getting this exception because the #JsonProperty("amount") is used on 2 seperate class fields.
It is best to create 2 different response:
Example:
class FrontEndResponse1 {
#JsonProperty("amount")
private int amount;
//getter/setter
}
class FrontEndResponse2 {
#JsonProperty("amount")
private String amount;
//getter/setter
}

Yes, this won't work with typed return values.
Unless you're fine with providing 2 endpoints with different return types you could return a plain Map<String, Object> instead of Response:
public Map<String, Object> awkwardMethod(boolean wantString) {
Response response = new Response();
... // do business logic
return Map.of("amount", wantString ? String.value(response.amount) : response.amount);
}
To be clear: I'm not encouraging you to do this as you'll loose type information for the return value and introduce error prone manual mapping. I'm just providing a solution for a problem that should be fixed by providing a clear API and clients sticking to this API.

Related

Jackson annotations - Unmarshall array as object by mapping index to property

I am reading from a stream that provides updates to an order book used to calculate market depth. Each includes a list of new entries for the order book. Each entry contains three properties.
Market side (e.g. buy or sell)
Quantity transacted of the commodity
Unit price of the commodity in this transaction
These entries are represented in JSON as array nodes. Here is an example of how entries might be provided.
{
"Changes": [
{ "entry": ["buy","470.84724800000004","16.14963"] },
{ "entry": ["buy","470.787392","0.01"] },
{ "entry": ["sell","473.112752","9.325423"] },
{ "entry": ["sell","473.052608","11.80723"] }
],
...some more fields; not relevant to this question...
}
As you can see, the indices of each entry array are used as field names. The position of each array element defines what property it represents. The side is at index 0, the unit price is at index 1, and the quantity is at index 2.
How can I de/serialize these using arrays Jackson annotations in Java 8? I am only asking about the innermost arrays. I don't need help with the object structure in general.
I tried making a class similar to the following.
public class OrderBookEntry {
final String side;
final BigDecimal price;
final BigDecimal quantity;
#JsonCreator
public OrderBookEntry(#JsonProperty(index = 0, required = true) String side,
#JsonProperty(index = 1, required = true) BigDecimal price,
#JsonProperty(index = 2, required = true) BigDecimal quantity) {
this.side = side;
this.price = price;
this.quantity = quantity;
}
}
I have tried specifying #JsonFormat.Shape.ARRAY on the class. Every time I try to deserialize a sample string, I get an InvalidDefinitionException.
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Invalid type definition for type com.example.OrderBookEntry: Argument #0 has no property name, is not Injectable: can not use as Creator [constructor for com.example.OrderBookEntry, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=#com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]
at [Source: (String)"["buy","470.84724800000004","16.14963"]"; line: 1, column: 1]
Is there not a way to do this with only annotations?
P.S. (rant)
I'd just like to add, this is an absurd data structure. It makes no sense. The purpose of using array indices instead of object field names would be to reduce the size of messages. This is a financial data stream, and any improvement to the network latency of financial data is desirable. But, around each of these arrays is a completely superfluous wrapper object with a single named field. This adds at least 10 bytes of unnecessary traffic per entry. The data structure has a very poor design.
There is more than one way to do this but I always prefer the combination of a dedicated class with a dedicated serializer. Other options would be:
registering the serializer with ObjectMapper – explicit coding instead of (meta level) annotations
use a general set method (with Map<String, Object>) – hides the serializer's aspect in a lengthy method
mapping JsonNode with setter in parent class – like #2
Instead:
#JsonDeserialize(using = OrderBookEntryDeserializer.class)
public class OrderBookEntry {
// no further Jackson-annotations necessary
}
And the serializer:
class OrderBookEntryDeserializer extends StdDeserializer<OrderBookEntry> {
OrderBookEntryDeserializer() {
super(OrderBookEntry.class);
}
#Override
public OrderBookEntry deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
final JsonNode node = p.getCodec().readTree(p);
// now follow Jackson API to turn the node into
// an ArrayNode, there are plenty of is-methods
// and it requires a hard cast.
}
My example uses a package private deserializer class which works absolutely fine and locks it in its package (preferrably next to the OrderBookEntry).

How to POST a List as Body in Retrofit 2.6

Already searched a lot but couldn't find an appropriate answer. I have the following JSON. I understand this is a List object. How do I send a POST request as #Body in retrofit 2? Also, what is the POJO that I need to have to get a successful response from the API.
Please note that I have looked into all JSONObject based solutions. I am looking only for POJO based solutions where List/fields are sent as constructors.
{
"ring":[
{
"ring_pitch_id":"xxxx-xxxx-xxxx-xxxx",
"ring_match_id":"xxxx-xxxx-xxxx-xxxx",
"name":"xxxx",
"type":"xxxx",
"status":"xxxx"
}
]
}
Here is your pojo.
This is body:
public class RingBody {
List<RingModel> ring = new ArrayList<RingModel>();
}
This is items of list of your body.
public class RingModel {
#SerializedName("ring_pitch_id")
String ringPitchId;
#SerializedName("ring_match_id")
String ringMatchId;
String name;
String type;
String status;
}
The way I resolved this is by creating two model classes, ringList and ring. Similar to Ionut's answer, the ringList class contained List with setter. The ring model class contained all the 5 fields with getters and setters. In the calling method had the code that created object of ring class passing all 5 parameters and creating a list of it by writing List<ring> temp = new ArrayList<>();. temp.add(object_of_ring);. Creating object of ringList class and passing ring either as constructer or setter worked.

Changing json to java pojo

Let's say I have a JSON file example.json
example.json
{
"BaggageMaxSize" : {
"mesurement" : "sum",
"width" : 70,
"height" : 50,
"depth" : 40
}
}
And create the POJO class:
public class pojoexample{
private BaggageMaxSize BaggageMaxSize;
// getter
// setter
}
And then:
public class BaggageMaxSize
{
private String height;
private String width;
private String depth;
private String mesurement;
// getter
// setter
}
Now, I want to use the mapper.readValue to change file to BaggageInfoPolicy.class:
BaggageInfoPolicy bip = mapper.readValue(file, BaggageInfoPolicy.class);
But bip.getBaggageMaxSize().getMesurement() returns null value. Any suggestions?
Try using mapper.writeValue first and check how your resulting JSON object will look like. Very likely, there's an issue with int -> string conversion in your BaggageMaxSize when deserialized from JSON.
Also, check your getters/setters to be publicly visible and be available both on pojoexample and BaggageMaxSize.
Actually your JSON represents a pojoexample class instance and not a BaggageInfoPolicy object, which you haven't shared in your post.
So you need to change your code to:
PojoExample bip = mapper.readValue(file, PojoExample.class);
So it reads the PojoExample object correctly.
Note:
Your class should follow the java naming convention and
start with an uppercase, that's why I changed it to PojoExample,
change it in the class definition as well.
And Make sure your class fields have the same types as in the JSON, and their getters and setters are correctly implemented.

Retrieving Object without a Certain Variable

I'm working on a small Java RESTful API and a client has asked for a certain object to have two different return options.
When a request is made to (as an example) /api/v1/products they want it to return just the "id" and "title" as opposed to the full object below.
When a request is then made to /api/v1/products/[productId] they want it to return the full object as well as any nested objects.
public class Product {
private int id;
private String title;
private String description;
private int weight;
private List<Price> prices;
}
Is it possible to dynamically create an object with only certain variables? They want it so that the other variables are never returned (for example in a JSON response they wouldn't want to see null values).
Am I right in thinking that the full object should be returned in the collection of products (/api/v1/products)?

Why isn't Gson parsing my document with nested escaped JSON string?

I'm having an annoying problem with GSON.
I can't get this document to parse:
[{"id":0,"assetId":2414775,"shipId":717,"assetType":"0","document":"{\"ratios\":[{\"points\":{\"x1\":0,\"y1\":673,\"x2\":3744,\"y2\":4408},\"crop\":[{\"name\":\"1_1\",\"width\":3744,\"height\":3735,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_1_1.jpg\"},{\"name\":\"x-supersquare\",\"width\":1024,\"height\":1024,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x-supersquare.jpg\"},{\"name\":\"x-square\",\"width\":98,\"height\":98,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x-square.jpg\"},{\"name\":\"x48\",\"width\":48,\"height\":48,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x48.jpg\"}],\"name\":\"1_1\",\"basename\":null,\"cropped\":true},{\"points\":{\"x1\":0,\"y1\":842,\"x2\":3744,\"y2\":3650},\"crop\":[{\"name\":\"4_3\",\"width\":3744,\"height\":2808,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_4_3.jpg\"},{\"name\":\"x-superlarge\",\"width\":2048,\"height\":1536,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x-superlarge.jpg\"},{\"name\":\"x-extralarge\",\"width\":1024,\"height\":768,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x-extralarge.jpg\"},{\"name\":\"x-large\",\"width\":490,\"height\":368,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x-large.jpg\"},{\"name\":\"x\",\"width\":245,\"height\":184,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x.jpg\"},{\"name\":\"x86\",\"width\":115,\"height\":86,\"path\":\"1371046546001-RCI-Test-Seas-1306121019_x86.jpg\"}],\"name\":\"4_3\",\"basename\":null,\"cropped\":true},{\"points\":{\"x1\":0,\"y1\":322,\"x2\":3744,\"y2\":5307},\"crop\":[{\"name\":\"3_4\",\"width\":3744,\"height\":4985,\"path\":\"1371046546001-RCI-Test-Seas-1306121020_3_4.jpg\"},{\"name\":\"x-3-4\",\"width\":768,\"height\":1024,\"path\":\"1371046546001-RCI-Test-Seas-1306121020_x-3-4.jpg\"}],\"name\":\"3_4\",\"basename\":null,\"cropped\":true},{\"points\":{\"x1\":0,\"y1\":1755,\"x2\":3744,\"y2\":3861},\"crop\":[{\"name\":\"16_9\",\"width\":3744,\"height\":2106,\"path\":\"1371046546001-RCI-Test-Seas-1306121020_16_9.jpg\"},{\"name\":\"x-16-9super\",\"width\":2048,\"height\":1162,\"path\":\"1371046546001-RCI-Test-Seas-1306121020_x-16-9super.jpg\"},{\"name\":\"x-16-9\",\"width\":1280,\"height\":720,\"path\":\"1371046546001-RCI-Test-Seas-1306121020_x-16-9.jpg\"}],\"name\":\"16_9\",\"basename\":null,\"cropped\":true},{\"points\":{\"x1\":295,\"y1\":0,\"x2\":3448,\"y2\":5616},\"crop\":[{\"name\":\"9_16\",\"width\":3153,\"height\":5616,\"path\":\"1371046546001-RCI-Test-Seas-1306121020_9_16.jpg\"},{\"name\":\"x-9-16\",\"width\":720,\"height\":1280,\"path\":\"1371046546001-RCI-Test-Seas-1306121020_x-9-16.jpg\"}],\"name\":\"9_16\",\"basename\":null,\"cropped\":true},{\"points\":{\"x1\":0,\"y1\":1221,\"x2\":3744,\"y2\":4408},\"crop\":[{\"name\":\"front_thumb\",\"width\":3744,\"height\":3187,\"path\":\"1371046546001-RCI-Test-Seas-1306121021_front_thumb.jpg\"},{\"name\":\"x-front-thumb\",\"width\":115,\"height\":98,\"path\":\"1371046546001-RCI-Test-Seas-1306121021_x-front-thumb.jpg\"}],\"name\":\"front_thumb\",\"basename\":null,\"cropped\":true}],\"attributes\":[{\"name\":\"oImageHeight\",\"value\":\"5616\"},{\"name\":\"oImageWidth\",\"value\":\"3744\"},{\"name\":\"sImageHeight\",\"value\":\"400\"},{\"name\":\"sImageWidth\",\"value\":\"266\"},{\"name\":\"imageCropStatus\",\"value\":\"1_1:manual; 4_3:manual; 3_4:manual; 16_9:manual; 9_16:manual; front_thumb:manual; \"},{\"name\":\"credit\",\"value\":\"Michel Verdure\"},{\"name\":\"alternate\",\"value\":\"Image of Royal Caribbean Allure of the Sease\"},{\"name\":\"title\",\"value\":\"Allure of the Seas\"},{\"name\":\"caption\",\"value\":\"Allure of the Seas - At sea, by the coast line of Miami\\nAllure of the Seas - Royal Caribbean International\"},{\"name\":\"datephototaken\",\"value\":\"11-24-2010\"},{\"name\":\"cutline\",\"value\":\"Allure of the Seas - At sea, by the coast line of Miami\\nAllure of the Seas - Royal Caribbean International\"},{\"name\":\"orientation\",\"value\":\"horizontal\"},{\"name\":\"OrigImageName\",\"value\":\"RCI Allure of the Seas.jpg\"},{\"name\":\"imgIndex\",\"value\":\"1371046546001\"},{\"name\":\"imgUniqueName\",\"value\":\"1371046546001-RCI-Test-Seas.jpg\"},{\"name\":\"isImageVisitedByUser\",\"value\":\"True\"},{\"name\":\"isAutoCroppedSetOn\",\"value\":\"True\"},{\"name\":\"publishurl\",\"value\":\"http://www.gannett-cdn.com/media\"},{\"name\":\"rootpath\",\"value\":\"/TEST/GenericImages/2013/06/12/\"},{\"name\":\"basename\",\"value\":\"1371046546001-RCI-Test-Seas.jpg\"},{\"name\":\"smallbasename\",\"value\":\"1371046546001-RCI-Test-Seas_small.jpg\"}],\"contributors\":[],\"id\":2414775,\"propertyID\":1,\"siteid\":1,\"sitename\":\"TEST\",\"assetGroupId\":1,\"assetGroupName\":\"TEST\",\"type\":\"image\",\"typeid\":1,\"typeidSpecified\":true,\"position\":0,\"positionSpecified\":true,\"priority\":null,\"handling\":null,\"status\":\"published\",\"dates\":{\"embargodate\":\"0001-01-01T00:00:00-05:00\",\"feeddateline\":\"0001-01-01T00:00:00-05:00\"},\"TaxonomyEntities\":[],\"assetdocument\":null,\"lastaction\":null,\"CreateDate\":\"2013-06-12T10:19:15.607-04:00\",\"UpdateDate\":\"2013-06-12T10:19:39-04:00\",\"CreateUser\":\"nrifken\",\"UpdateUser\":\"nrifken\",\"LastPublishedDate\":\"2013-06-12T10:19:15.607-04:00\"}"}]
You can use an online tool to view it better (i.e. http://json.parser.online.fr/)
As you can see the "document" is a nested JSON as string and somehow GSON is trying to parse that when I think it shouldn't, I tried setting my Serializable member as String, Object, Map, Map<Object, Object>, etc. but none of them seems to work. Also tried setting it to transient or removing to see if GSON wouldn't bother parsing but still I get:
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 72
I think I don't have much control of the deserialization since I'm using RestTemplate's GsonHttpMessageConverter. so I think it's mostly e GSON issue than Springs.
My Model:
public class ShipAsset {
public ShipAsset(String pID, Integer pAssetID,
Integer pShipID, String pDocument, String pAssetType) {
super();
mID = pID;
mAssetID = pAssetID;
mShipID = pShipID;
mDocument = pDocument;
mAssetType = pAssetType;
}
public ShipAsset() {
}
#SerializedName("id")
private String mID;
#SerializedName("assetId")
private Integer mAssetID;
#SerializedName("shipId")
private Integer mShipID;
#SerializedName("document")
private String mDocument;
#SerializedName("assetType")
private String mAssetType;
/* getters and setters */
}
That's how I'm calling it:
ShipAsset[] oResults = mGson.fromJson(oJSONString, ShipAsset[].class);
Anyone have any ideas?
Thanks
The error must be in one of this three things:
1) You are receiving a JSONArray, you are casting like this don't you?:
public class RestFactory {
public static ShipAsset[] getShipsAssets(RestClient restClient) throws Exception {
ResponseEntity<ShipAsset[]> response = restClient.getForEntity(
"http://server/rest/yourmethod", ShipAsset[].class);
return response.getBody();
}
}
2) I'm not sure if It's by this, but you don't have a constructor with all the fields neither an empty contructor. Maybe because isn't a full pojo, Gson isn't casting rightly.
3) Try to annotate the fields id and assetType. I'm pretty sure that if you don't annotate a field with the SerializedName annotation, Gson ignores it by default. But anyways, give it a try!.
Good luck!

Categories

Resources