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).
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.
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.
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!