Form pojo to parse JSON - java

My json looks like this :
{
"bid": "181.57",
"ask": "181.58",
"volume": {
"item1": "543.21",
"item2": "123.45",
"timestamp": 1487903100000
},
"last": "181.58"
}
I'm trying to use spring restTemplate to read it into a pojo. My current pojo is this :-
import com.fasterxml.jackson.annotation.JsonProperty;
public class DataModel {
private String last;
private Volume volume;
private String ask;
private String bid;
// Getter and setters
}
class Volume
{
private String timestamp;
#JsonProperty
private String item1;
#JsonProperty
private String item2;
// Gettersand setters
}
The problem is that "item1" and "item2" int the json can change to "item5" and "item6" depending on which entity I am querying for. I get null values if my variables are named item1 and item2. How can I keep generic names for the variables item1 and item2 and still be able to read the values correctly in the generic variables? Is there any annotation that will help here?

I believe this is what you are looking for from a Baeldung tutorial:
3.3. #JsonAnySetter
#JsonAnySetter allows you the flexibility of using a Map as standard properties. On de-serialization, the properties from JSON will simply be added to the map.
Let’s see how this works – we’ll use #JsonAnySetter to deserialize the entity ExtendableBean:
public class ExtendableBean {
public String name;
private Map<String, String> properties;
#JsonAnySetter
public void add(String key, String value) {
properties.put(key, value);
}
}
This is the JSON we need to deserialize:
{
"name":"My bean",
"attr2":"val2",
"attr1":"val1"
}
And here’s how this all ties in together:
#Test
public void whenDeserializingUsingJsonAnySetter_thenCorrect()
throws IOException {
String json
= "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}";
ExtendableBean bean = new ObjectMapper()
.readerFor(ExtendableBean.class)
.readValue(json);
assertEquals("My bean", bean.name);
assertEquals("val2", bean.getProperties().get("attr2"));
}
In your case, you would simply query the map for the String values you expect for whichever query you are making.

Related

How to access a JSON object of nested structure sent by Angular to Spring boot backend?

Suppose I have a post http request from angular having a folllowing JSON structure :
{
"abc":{
"pqr":2,
"lmn":5,
"xyz":89
},
"def":[a,b,c,d],
"klm":{
//object attributes
}
}
which gets sent as a post request from angular HttpClient.
Now in spring boot Controller I am accepting it using a Hashmap of
#PostMapping("/createXyzFunctionality")
public void createXyzFunctionality(#RequestBody Map<String, Object> json)
{
for (Map.Entry<String, Object> entry : json.entrySet())
{
//Using entry.getKey() and entry.getValue() I can access the attributes
//"abc","def","klm" as string but I want to access as class objects
.....
}
}
Now, I have a model class for "abc" but isn't exactly the instance of my class, so when I do
CustomClass val = (CustomClass) entry.getValue();
I got ClassCastException, Help me access the attributes of the Objects in hashmap without changing the models in spring boot.
CustomClass{
private Integer pqr,lmn,xyz;
private String extraVariable;
//getters setters
}
I want pqr,lmn,xyz to get values from "abc".
Instead of #RequestBody Map<String, Object> json you should expect an object of the class in RequestBody.
So create a set of DTOs:
public class BodyClass {
private Abc abc;
private List<String> def;
private Klm klm;
//getters & setters
}
public class Abc {
private Integer pqr;
private Integer lmn;
private Integer xyz;
}
public class Klm {
//some parameters, getters & setters
}
And accept #RequestBody BodyClass bodyClass, e.g.:
#PostMapping("/createXyzFunctionality")
public void createXyzFunctionality(#RequestBody BodyClass bodyClass) {
//your logic here
}
bodyClass will contain all the attributes of the JSON you're sending.

No primary or default constructor found for interface java.util.List Rest API Spring boot

I am passing a request body to a POST request on postman similar to this:
"name":"Mars",
"artifacts":[
{
"elements":[
{
"name":"carbon",
"amount":0.5,
"measurement":"g"
}
],
"typeName":"typeA"
},
{
"elements":[
{
"name":"hydrogen",
"amount":0.2,
"measurement":"g"
}
],
"typeName":"typeB"
}
]
The create method in the rest controller looks like this.
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars;
Planet and all its nested objects have a default constructor such as:
public Planet() {}
However, I am not able to create a new planet object because of lack of a default constructor. Please help!
EDIT:
Planet class
public class Planet {
#JsonProperty("name")
private String name;
#Field("artifacts")
private List<Artifact> artifacts;
public Planet() {}
public Planet(String name, List<Artifact> artifacts)
{
this.name = name;
this.artifacts = artifacts;
}
//setters and getters
}
Artifact class:
public class Artifact() {
#Field("elements")
private List<Element> elements;
#JsonProperty("typeName")
private String typeName;
public Artifact() {}
public Artifact(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
}
Element class:
public class Element() {
#JsonProperty("elementName")
private String name;
#JsonProperty("amount")
private double amount;
#JsonProperty("measurement")
private String measurement;
public Element() {}
public Element(String name, double amount, String measurement)
{
//assignments
}
}
I had that the same error when I forgot the #RequestBody before the parameter
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
I don't understand what is the issue you are facing, but i can see an error straight away so guessing that is the issue you are facing, i am going to give you a solution.
Create a class which matches your json data structure like this :
Class PlanetData {
private String name;
private List<Planet> artifacts;
public PlanetData(String name, List<Planet> artifacts){
name = name;
artifacts = artifacts;
}
// include rest of getters and setters here.
}
Then your controller should look like this. Basically you needed to put #RequestBody to all the parameters you want to recieve from request JSON. Earlier you only put #RequestBody to name parameter not artifact parameter and since Request Body can be consumed only once, so you need a wrapper class to recieve the complete request body using single #RequestBody annotation.
#RequestMapping("/create")
public String create(#RequestBody PlanetData data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars.toString();
}
Edit : Looking at the Planet class, it also needs some modification
public class Planet {
private String typeName; // key in json should match variable name for proper deserialization or you need to use some jackson annotation to map your json key to your variable name.
private List<Element> elements;
public Planet() {}
public Planet(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
//setters and getters. Remember to change your setters and getter from name to typeName.
}
Hope this solves your issue.
This answer too might help someone.
When you are using spring framework for your API development, you may accidently import a wrong library for RequestBody and RequestHeader annotations.
In my case, I accidently imported library,
io.swagger.v3.oas.annotations.parameters.RequestBody
This could arise the above issue.
Please ensure that, you are using the correct library which is
org.springframework.web.bind.annotation.RequestBody
I guess, it’s trying to call new List() which has no constructor. Try using ArrayList in your signatures.
If it works this way, you have found the error. Then rethink your concept of calling methods, since you would usually want to avoid using implementations of List in method signatures
Make sure your request type is not of type GET
If so it is better not to send data as request body.
you should write as below:
...
public String create(#RequestBody JSONObject requestParams) {
String name=requestParams.getString("name");
List<Planet> planetArtifacts=requestParams.getJSONArray("artifacts").toJavaList(Planet.Class);
...

Deserializing json with a large amount of properties into java class via Jackson annotation

So I am currently using Jackson to deserialize JSON into java objects. Everything works well but I also have some fields such as:
For this kind of JSON file:
{
"uid": 7,
"asset": 123,
"currency1_balance": 0,
"currency2_balance": 0,
... ...
"currencyN_balance": 0,
}
we can deserialize it via the java class below:
#Test
public void testBalanceJSON() throws IOException {
// Read in the JSON from the example resources
InputStream is = BalanceTest.class.getResourceAsStream("/example-balance-data.json");
//Use Jackson to parse it
ObjectMapper mapper = new ObjectMapper();
Balance balance = mapper.readValue(is, Balance.class);
}
... ...
#JsonIgnoreProperties(ignoreUnknown = true)
public class Balance {
private final BigDecimal uid;
private final BigDecimal asset;
private final BigDecimal currency1Balance;
private final BigDecimal currency2Balance;
// ... ...
private final BigDecimal currencyNBalance;
#JsonCreator
public Balance(#JsonProperty("uid") BigDecimal uid, #JsonProperty("asset") BigDecimal asset,
#JsonProperty("currency1_balance") BigDecimal currency1Balance,
#JsonProperty("currency2_balance") BigDecimal currency2Balance,
// ... ...
#JsonProperty("currencyN_balance") BigDecimal currencyNBalance) {
this.uid = uid;
this.asset = asset;
this.currency1Balance = currency1Balance;
this.currency2Balance = currency2Balance;
// ... ...
this.currencyNBalance = currencyNBalance;
}
}
In above case, I have to write lots of code to deal with the mapping between "currencyN_balance" and "currencyNBalance". Once a currency is added/removed, I'll have to modify the code accordingly, which is quite inflexible.
Then I found the following way to customize the deserialization process.
#JsonCreator
public static Balance Create(Map<String, Object> jsonMap) {
//Resovle the map
BigDecimal uid = new BigDecimal(jsonMap.get("uid").toString());
BigDecimal asset = new BigDecimal(jsonMap.get("asset").toString());
for (Map.Entry<String, Object> entry : jsonMap.entrySet()){
if (entry.getKey().startsWith("currency")) {
//Do something...
}
}
//Then construct the Balance instance and return
}
By using the above method, I can get rid of the boring and error-prone mapping. But I can't leverage the annotation to deal with some parameters' deserialization(e.g. uid, asset).
Is there any ways combining the strengths of both techniques above?
(using annotation with parameter such as uid, asset while being able to customize the currency mapping)
Thanks.
That's a JSON abomination. That currency_[1..n]_balance should be in an array... as it's an array but encoded in a crappy way.
Anyway, it's not your fault (I hope). For this you can use the Jackson #JsonAnyGetter and #JsonAnySetter annotations (if you want to serialise and deserialise this class, otherwise you can use only one of them).
For example
#JsonAnySetter
public void set(String name, String value) {
//do funky transformation/assigment
}
Jackson will call that method with any property that it cannot map. So you might need to add some logic to ignore properties that you are not interested.

How do I deserialize this JSON using Jackson?

I have json that looks like this:
{
"summary":{
"somefield1":"somevalue1",
"Twilio":{
"field1":"value1",
"field2":"value2"
},
"Tropo":{
"field1":"value1",
"field2":"value2"
},
...
}
}
I would like to deserialize it into a java class that looks like this:
public class Summary {
private String someField1;
private List<Vendor> vendors;
}
public class Vendor {
private String name;
private String field1;
private String field2;
}
So the Twilio and Tropo need to become Vendor objects in a list where Vendor.name == "Twilio" or "Tropo".
I'm sure jackson has the tools I need to work with this structure but I've been striking out with web searches.
You can do it with combination of #JsonRootName and #JsonAnySetter annotations. Your Summary class should look like this:
#JsonRootName("summary")
class Summary {
#JsonProperty("somefield1")
private String someField1;
private List<Vendor> vendors = new ArrayList<Vendor>();
#JsonAnySetter
public void setDynamicProperty(String vendorName, Map<String, String> properties) {
Vendor vendor = new Vendor();
vendor.setName(vendorName);
vendor.setField1(properties.get("field1"));
vendor.setField2(properties.get("field2"));
vendors.add(vendor);
}
//getters,setters,toString methods
}
Example usage:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
System.out.println(mapper.readValue(json, Summary.class));
Above source code shows below string for your JSON:
Summary [someField1=somevalue1, vendors=[Vendor [name=Twilio, field1=value1, field2=value2], Vendor [name=Tropo, field1=value1, field2=value2]]]
If you want to use your objects:
public class Summary {
private String someField1;
private List<Vendor> vendors;
}
public class Vendor {
private String name;
private String field1;
private String field2;
}
you have to modify your json. Actually a structure like the one you defined will be converted to something like:
{
"summary": {
"somefield1": "somevalue1",
"vendors": [
{
"name": "Twilio",
"field1": "value1",
"field2": "value2"
},
{
"name": "Tropo",
"field1": "value1",
"field2": "value2"
}
]
}
}
a list is defined between the square brackets [], and in your case it's a list of objects {}.
I would change your json if you can, because the structure you post will be a mess to work with. The one I pointed out, that matches your java objects, is more clear.
The JSON structure you've got would match this java structure where the key of vendors is the vendor name.
public class Summary {
private String someField1;
private Map<String,Vendor> vendors;
}
public class Vendor {
private String field1;
private String field2;
}
The classes you've specified would support this JSON:
{
"somefield1":"somevalue1",
"vendors":[{
"name":"Twilio"
"field1":"value1",
"field2":"value2"
},
{
"name":"Tropo"
"field1":"value1",
"field2":"value2"
},
...]
}
I don't think you can achieve what you want with jackson as the name is outside the "Vendor" object.

Java Jackson writing object twice

I have the following class which contains a String field and a Map field. I want to use Jackson to serialize it to json.
public class Mapping
private String mAttribute;
#JsonIgnore
private Map<String, String> mMap;
#JsonAnyGetter
public Map<String, String> getMap() {
//some logic to populate map
}
#JsonAnySetter
public void put(// some params) {
//some more logic
}
#JsonProperty(value = "attribute")
public String getAttribute() {
return mAttribute;
}
public void setAttribute(String aAttribute) {
mAttribute= aAttribute;
}
}
I instantiate a Mapping object and then use ObjectMapper to write it to a file.
ObjectMapper om = new ObjectMapper();
om.writeValue(destFile, myMappingObject);
For some reason, it's writing the Mapping instance myMappingObject twice. I'm assuming I've not set some visibility option somewhere but I don't know where.
The json looks like this, only it comes up twice in the file.
{
"attribute" : "someValue",
"map-key1" : "map-value1",
"map-key2" : "map-value2"
}
There's this, but apparently it was fixed in previous version of Jackson. I also tried changing the name of the method to random() and it still gets called twice (the number of times it should).
The problem had nothing to do with the above class. I was using another class that had a list of Mappings. Before:
public class MappingsList {
#JsonProperty
private List<Mapping> mappings;
public List<Mapping> getMappings() {return mappings;}
}
After:
public class MappingsList {
private List<Mapping> mappings;
#JsonProperty
public List<Mapping> getMappings() {return mappings;}
}
And it worked. The cause is that the ObjectMapper was seeing two (2) properties in the MappingsList class and therefore doing serialization on both. First it would create json for the mappings field and then again for the getMappings() method.

Categories

Resources