This is a follow up question to this question:
Passing custom type query parameter
I got a class which includes this method:
public static MyClass fromString(String json)
{
Gson gson = new Gson();
MyClass user = gson.fromJson(json, MyClass.class);
return user;
}
The full class:
public class MyClass
{
public String name;
public PortalNameEnum portalName;
public PortalUserTypeEnum portalUserType;
public String notes;
public MyClass(String name, PortalNameEnum portalName,
PortalUserTypeEnum portalUserType, String notes)
{
super();
this.portalName = portalName;
this.portalUserType = portalUserType;
this.name = name;
this.notes = notes;
}
public static MyClass fromString(String json)
{
Gson gson = new Gson();
PortalUserInfo user = gson.fromJson(json, PortalUserInfo.class);
return user;
}
public PortalNameEnum getPortalName()
{
return portalName;
}
public void setPortalName(PortalNameEnum portalName)
{
this.portalName = portalName;
}
public PortalUserTypeEnum getPortalUserType()
{
return portalUserType;
}
public void setPortalUserType(PortalUserTypeEnum portalUserType)
{
this.portalUserType = portalUserType;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getNotes()
{
return notes;
}
public void setNotes(String notes)
{
this.notes = notes;
}
}
I got a resource which got a method:
#Path("/myclasscall")
#GET
#UnitOfWork
public String registerPortalUser(#Context HttpServletRequest req, #QueryParam("callback") String callback, #QueryParam("myclass") MyClass recordData) throws Throwable
{ .. }
It seems like the fromString method is not called and the resource method is always null, even though I see in the console the request itself and I do see a string that has been passed. Why is that?
The problem was with the client.
Instead of passing a single parameter called "myclass", he passed all the fields separately. After merging them together into a single Json instance, it was fixed.
Related
I want to get the country details from an external api and using Gson to set the data received from the get request to class Country. The problem is that in the response, the currencies key has value which is between [](please see below) and in some cases there is a space between the currencies name values which causes the following error
com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 41 path $.currencies[0].name:
"currencies":[{"code":"BGN","name":"Bulgarian lev","symbol":"лв"}]
#RestController
public class CountryController {
#Autowired
private RestTemplate restTemplate;
private static String baseURL = "https://restcountries.com/v2/";
public Object[] getCountryDetails(String countryName){
Object[] countryDetails = restTemplate.getForObject(baseURL+"name/"+countryName+"?fields=name,alpha2Code,alpha3Code,capital,currencies", Object[].class);
return countryDetails;
}
public Country createCountryObject(String countryName) {
String response = Arrays.asList(getCountryDetails(countryName)).get(0).toString();
Gson g = new Gson();
JsonReader reader = new JsonReader(new StringReader(response.trim()));
reader.setLenient(true);
Country country = g.fromJson(reader, Country.class);
return country;
}
#GetMapping("/")
public String getAll(){
Country country = createCountryObject("bulgaria");
return country.getName();
}
}
Country.java:
package country.neighbours.tour.models;
import java.util.List;
public class Country {
private String name;
private String alpha2Code;
private String alpha3Code;
private List<String> borders;
private Object[] currencies;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getBorders() {
return borders;
}
public void setBorders(List<String> borders) {
this.borders = borders;
}
public String getAlpha2Code() {
return alpha2Code;
}
public void setAlpha2Code(String alpha2Code) {
this.alpha2Code = alpha2Code;
}
public String getAlpha3Code() {
return alpha3Code;
}
public void setAlpha3Code(String alpha3Code) {
this.alpha3Code = alpha3Code;
}
public Object[] getCurrencies() {
return currencies;
}
public void setCurrencies(Object[] currencies) {
this.currencies = currencies;
}
}
How can I get only the currency code?
It looks like you are parsing the response twice; once with restTemplate.getForObject, then you convert its result to a String (the result of your toString() call is most likely not JSON) and then you try to parse it a second time with Gson.
In case you only want to use Gson, you can use a TypeToken in the fromJson call to parse the response JSON array:
List<Country> countries = gson.fromJson(..., new TypeToken<List<Country>>() {}.getType());
Maybe someone more familiar with Spring can also explain how to use only RestTemplate.getForObject for this instead of Gson.
Using Retrofit here to consume Google Civic API.
The library requires you to create a model of what the API will return as I have done already with Election. Which is basically a copy of the google documentation.
(Retrofit binds the response properties to properties with the same name)
Election.Java :
public class Election {
private long id;
private String name;
private String electionDay;
private String ocdDivisionId;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getElectionDay() {
return electionDay;
}
public void setElectionDay(String electionDay) {
this.electionDay = electionDay;
}
public String getOcdDivisionId() {
return ocdDivisionId;
}
public void setOcdDivisionId(String ocdDivisionId) {
this.ocdDivisionId = ocdDivisionId;
}
}
But Representatives have an inconsistent property name, thus I don't see a way to model this in a way Retrofit will know how to deserialize the API's response.
Representatives object (JSON) :
property name is called (key)
How do I let Retrofit deserialize a model that captures the property named variable after a key of the division?
Assuming you're using a Gson converter, I personally would use a map. I guess the same can be achieved with other converters, but I never used them. Say you have the following object:
public class Division {
#SerializedName("name")
#Expose
private String name;
#SerializedName("alsoKnownAs")
#Expose
private List<String> alsoKnownAs = new ArrayList<>();
#SerializedName("officeIndices")
#Expose
private List<Integer> officeIndices = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getAlsoKnownAs() {
return alsoKnownAs;
}
public void setAlsoKnownAs(List<String> alsoKnownAs) {
this.alsoKnownAs = alsoKnownAs;
}
public List<Integer> getOfficeIndices() {
return officeIndices;
}
public void setOfficeIndices(List<Integer> officeIndices) {
this.officeIndices = officeIndices;
}
}
Which represents the object inside the divisions array. You can then have the class:
private class Divisions {
#SerializedName("divisions")
#Expose
private Map<String, Division> divisions = new HashMap<>();
// ...
}
Notice the usage of a map here? Behind the scenes Gson will be able to serialise and deserialise your objects. The class Divisions is the root of the json you gave us in the question.
Hope this helps
I want to parse a JSON document with Jackson and apply some transformation on all nodes. For example, let's say that I want all values to be in uppercase after deserialization.
The actual use case is a bit more complex:
transformation is more complex, the transformer class need to be injected with some configuration, I'd like it to be a configureable instance
transformation has to happen on all properties, I'd like to be able to not add an annotation on each property of each class deserialized.
There are enough configuration options / hooks in Jackson, so I'm fairly sure that this is possible, I just can't find my way around.
The test below shows what I'm trying to achieve:
public class JsonValueFilterTest {
private ObjectMapper mapper;
#Before
public void setupObjectMapper() {
mapper = new ObjectMapper();
// TODO: configure mapper to upper case all values
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
Entity myEntity = mapper.readValue("{\"name\":\"myName\"}", Entity.class);
assertThat(myEntity.getName()).isEqualTo("MYNAME"); // fails
}
public static class Entity {
private final String name;
#JsonCreator
public Entity(#JsonProperty("name") String name) { this.name = name; }
public String getName() { return name; }
#Override
public String toString() { return "name='" + name + "'"; }
}
}
You can use converter for that simple case to not implement custom deserializer. I don't know why, but It's not working on the creator constructors, though. So you will have to use non-final fields.
public class JsonValueFilterTest {
private ObjectMapper mapper;
#BeforeTest
public void setupObjectMapper() {
mapper = new ObjectMapper();
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
Entity myEntity = mapper.readValue("{\"name\":\"myName\"}", Entity.class);
Assert.assertEquals(myEntity.getName(), "MYNAME"); // fails
}
public static class UpCaseConverter extends StdConverter<String, String> {
public String convert(String value) {
return value==null ? null : value.toUpperCase();
}
}
public static class Entity {
private String name;
public Entity() {}
public Entity(String name) {
this.name = name;
}
#JsonDeserialize(converter = UpCaseConverter.class)
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public String toString() {
return "name='" + name + "'";
}
}
}
My final solution (thanks to Alban):
configure the ObjectMapper with a custom JsonNodeFactory which transforms all text nodes
deserialize json to JsonNode (this will apply transformation)
convert the JsonNode to my custom class
public class JsonValueFilterTest {
private ObjectMapper mapper;
#Before
public void setupObjectMapper() {
mapper = new ObjectMapper();
mapper.setNodeFactory(new JsonNodeFactory() {
#Override
public TextNode textNode(String text) {
return super.textNode(text.toUpperCase());
}
});
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
JsonNode jsonNode = mapper.readTree("{\"name\":\"myName\"}");
Entity myEntity = mapper.treeToValue(jsonNode, Entity.class);
assertThat(myEntity.getName()).isEqualTo("MYNAME");
}
public static class Entity {
private final String name;
#JsonCreator
public Entity(#JsonProperty("name") String name) { this.name = name; }
public String getName() { return name; }
#Override
public String toString() { return "name='" + name + "'"; }
}
}
I am trying to assign the value returned by some function to a field in the deserialized class of json.
FileInfo.java
public class FileInfo {
#SerializedName("Name")
private String mName;
#SerializedName("Url")
private String mUri;
#SerializedName("Size")
private Integer mSize;
#SerializedName("ModTime")
private Long mModifiedTime;
private FileType mType;
#SerializedName("Children")
private ArrayList<FileInfo> mChildren = new ArrayList<>();
public ArrayList<FileInfo> getChildren() {
return mChildren;
}
public long getModifiedTime() {
return mModifiedTime;
}
public String getName() {
return mName;
}
public Integer getSize() {
return mSize;
}
public String getUrl() {
return mUri;
}
public FileType getType() {
return mType;
}
public void setChildren(ArrayList<FileInfo> mChildren) {
this.mChildren = mChildren;
}
public void setModifiedTime(long mModifiedTime) {
this.mModifiedTime = mModifiedTime;
}
public void setName(String mName) {
this.mName = mName;
}
public void setSize(Integer mSize) {
this.mSize = mSize;
}
public void setType(FileType mType) {
this.mType = mType;
}
public void setUri(String mUri) {
this.mUri = mUri;
}
#Override
public String toString() {
return FileInfo.class.toString();
}
public FileInfo() {
}
}
The mType needs to be assigned to foo(mName). I looked up custom deserializers and instance creators but none of those helped. I also thought of TypeAdapters which i feel defeats the purpose of keeping deserialization(using GSON) simple.
This is a sample JSON string that will be deserialized.
[
{
"Name":"Airport",
"Url":"http://192.168.2.2/api/sites/Baltimore%20Airport/Airport",
"Size":0,
"ModTime":"2015-12-02T14:19:17.29824-05:00",
"Children":null
}
]
P.S. I'm not sure if this should be done during deserialization but trying anyways. Also please let me know of alternative ways to achieve this.
I am trying to write my own bean utils converter so that I can export my object to a plain text file
I have the main class
public class BeanUtilsTest {
public static void main(String[] args) {
try{
MyObject myObject = new MyObject();
myObject.setId(3l);
myObject.setName("My Name");
ConvertUtilsBean cub = new ConvertUtilsBean();
cub.deregister(String.class);
cub.register(new MyStringConverter(), String.class);
cub.deregister(Long.class);
cub.register(new MyLongConverter(), Long.class);
System.out.println(cub.lookup(String.class));
System.out.println(cub.lookup(Long.class));
BeanUtilsBean bub = new BeanUtilsBean(cub, new PropertyUtilsBean());
String name = bub.getProperty(myObject, "name");
System.out.println(name);
String id = bub.getProperty(myObject, "id");
System.out.println(id);
}catch(Exception ex){
ex.printStackTrace();
}
}
}
The Long Converter
public class MyLongConverter implements Converter{
#Override
public Object convert(Class clazz, Object value) {
System.out.println("Long convert");
return value.toString()+"l";
}
}
The String Converter
public class MyStringConverter implements Converter{
#Override
public Object convert(Class clazz, Object value) {
System.out.println("String convert");
return value.toString()+":";
}
}
Finally my object
public class MyObject {
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The Output
String convert
My Name:
String convert
3:
I was expecting the id will go through MyLongConverter, but it seems it is still going thru the String one. Why and how can I fix this?
Please advise
Thanks
String id = bub.getProperty(myObject, "id");
Above getProperty function in BeanUtilBean class has to return String representation of the property you requested, regardless of what format the property is defined. So, it will always use String converter (MyStringConverter).
Since the destination type here is always String, MyLongConverter will never be used.
Instead, MyStringConverter should inspect the type of the value parameter and accordingly convert it to String.