I get a json-answer from server. And i'm parsing it with GSON-library.
A key within json has an integer value. Is it somehow possible without to change the server answer (it is the external server interface, we have no influence on it) to cast the integer value to an enumeration?
Thank you.
UPD:
The json-Response. NOTE: we can't change it
"testObject":{
"id":123,
"type":42
}
The enumeration:
public enum ObjectTypeEnum
{
UNKNOWN_TYPE(0),
SIMPLE_TYPE(11),
COMPLEX_TYPE(42);
private int value;
private ObjectTypeEnum(int value)
{
this.value = value;
}
public static ObjectTypeEnum findByAbbr(int value)
{
for (ObjectTypeEnum currEnum : ObjectTypeEnum.values())
{
if (currEnum.value == value)
{
return currEnum;
}
}
return null;
}
public int getValue()
{
return value;
}
}
And the object class
public class TestObject
{
publuc int id;
public ObjectTypeEnum type;
}
You can just use the #SerializedName annotation to determine what value gets serialized to/from the wire. Then you don't need to write a custom TypeAdapter.
import com.google.gson.annotations.SerializedName;
public enum ObjectTypeEnum {
#SerializedName("0")
UNKNOWN_TYPE(0),
#SerializedName("11")
SIMPLE_TYPE(11),
#SerialziedName("42")
COMPLEX_TYPE(42);
private int value;
private ObjectTypeEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
If you don't have a need for getting the wire value in your code you can eliminate the "value" field and related code.
public enum ObjectTypeEnum {
#SerializedName("0")
UNKNOWN_TYPE,
#SerializedName("11")
SIMPLE_TYPE,
#SerialziedName("42")
COMPLEX_TYPE;
}
Using an answer from Chin and help from my workmate I get following solution.
I wrote an inner class in the parser class.
private static class ObjectTypeDeserializer implements
JsonDeserializer<ObjectTypeEnum>
{
#Override
public PreconditioningStatusEnum deserialize(JsonElement json,
Type typeOfT, JsonDeserializationContext ctx)
throws JsonParseException
{
int typeInt = json.getAsInt();
return ObjectTypeEnum
.findByAbbr(typeInt);
}
}
and created GSON-Object on following way:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ObjectTypeEnum.class, new ObjectTypeDeserializer() );
Gson gson = gsonBuilder.create();
http://sites.google.com/site/gson/gson-user-guide#TOC-Custom-Serialization-and-Deserializ
From kjones' answer, here's a Kotlin translation:
enum class ObjectTypeEnum(val value:Int) {
#SerializedName("0")
UNKNOWN_TYPE(0),
#SerializedName("11")
SIMPLE_TYPE(11),
#SerializedName("42")
COMPLEX_TYPE(42)
}
Or, without needing the Int values:
enum class ObjectTypeEnum {
#SerializedName("0")
UNKNOWN_TYPE,
#SerializedName("11")
SIMPLE_TYPE,
#SerializedName("42")
COMPLEX_TYPE
}
public enum Color {
GREEN(1), BLUE(2), RED(3);
private int key;
private Color(int key) {
this.key = key;
}
public static Color findByAbbr(int key) {
for (Color c : values()) {
if (c.key == key) {
return c;
}
}
return null;
}
}
I'm new to SO so I don't know how to add to Mur Votema's answer above, but just a small correction;
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ObjectTypeEnum.class, new ObjectTypeDeserializer() );
Gson gson = gsonBuilder.create();
Note; you need brackets to provide an instance of the class for the gsonBuilder.
Apart from that, great answer! Did exactly what I was looking for.
Related
I have an entity class with two fields.
#Entity(name = "additional_attributes")
class AdditionalAttributes {
#Id
private Integer id;
private String attributeName;
private Object attributevalue;
// getter and setter
// manytoone with mandatory table
}
The datatype of attributeValue is Object here, which means the value can be anything like integer/boolean/float.
How to handle this scenario to save the right value and again while fetching I should get the exact value (which was of type boolean/integer etc) ??
You should add attribute class marker field Class<?> attributeClass. Another approach is to create enum AttributeType and use it as marker field
#Entity(name = "additional_attributes")
class AdditionalAttributes {
#Id
private Integer id;
private String attributeName;
Class<?> attributeClass;
String attributevalue;
public void setAttribute(Object attribute){
attributeClass = attribute.getClass()
attributevalue = attribute.toString();
}
}
To set attribute use this:
Integer integerAttribute = 100;
additionalAttributes.setAttribute(integerAttribute);
Boolean booleanAttribute = true;
additionalAttributes.setAttribute(booleanAttribute);
and then there are two approaches:
1) Add to entity or service class common attribute parcer
public Object getAttribute() throws NumberFormatException {
if(attributeClass == Integer.class) {
return Integer.parseInt(attributevalue);
}
if(attributeClass == Boolean.class) {
return Boolean.parseBoolean(attributevalue);
}
//...
}
Usage:
Object attribute = additionalAttributes.getAttribute();
2) Or use pair of methods to get attribute
public boolean isIntegerAttribute() {
return attributeClass == Integer.class;
}
public Integer getIntegerAttribute() throws NumberFormatException {
return Integer.parseInt(attributevalue);
}
public boolean isBooleanAttribute() {
return attributeClass == Boolean.class;
}
public Boolean getBooleanAttribute() {
return Boolean.parseBoolean(attributevalue);
}
//...
Usage:
if(additionalAttributes.isIntegerAttribute()) {
Integer integerAttribute = additionalAttributes.getIntegerAttribute();
//...
}
if(additionalAttributes.isBooleanAttribute()) {
Boolean booleanAttribute = additionalAttributes.getBooleanAttribute();
//...
}
Here is an example:
class Person {
String name;
Address addressGiven;
//getters and setters
class Address {
#JsonProperty(name="stno")
private String StreetNo
#JsonProperty(name="type")
private AddressType addType;
public void setstno(String stno){
if (this.addressGiven==null)
addressGiven=new Address();
addressGiven.setStno(stno);
}
public void setType(String type) {
if (addressGiven==null){
addressGiven=new Address();
}
addressGiven.setType(AddressType.valueOf(type));
}
// other getters and setters
}
}
AddressType.java
Enum AddressType {
HOME,
OFFICE,
BUSINESS,
DEFAULT;
}
Two points to note before I go to my question:
Address in an inner class
the instance attribute addType is of enum type
when I serialize the object:
Person person = new Person();
Person.setStNo("1234");
person.setType("HOME");
ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(person);
System.out.println(body);
I expect:
{
"addressGiven:
{ "stno" : "1234",
"type" : HOME,
}
}
but what I get is this :
{ "streetNo" : "1234"}.
Three noticable differences
nested json is missing
streetNo but not stno is returned
No addressType is present.
why is the expected json (i.e inner not returned. am I missing some annotations anywhere?
I browsed through jackson docs. but could not figure out sooner. so here I am?
Jackson will automatically call the empty constructor on the object is serializing. the exception being if a constructor is annotated with #JsonCreator, or a builder class annotated with #JsonPOJOBuilder, and maybe another one im missing. i would remove the creation of Address and also the checking for null. dummy down those setters/getters.
ObjecMapper by default handles serialization of an Enum. i would suggest removing that manual conversion
#see DeserializationFeature.READ_ENUMS_USING_TO_STRING. default value is false which means that it uses Enum.valueOf to serialize the String into the correct value.
with all that being said, you are expecting something that doesnt match your code. Person does not have an attribute type, nor stNo. those are Address attributes. im curious to know how you get the output shown. see below for code and example output
class Person {
private String name;
private Address addressGiven;
public void setName(String name) { this.name = name; }
public void setAddressGiven(Address addressGiven) { this.addressGiven = addressGiven; }
public String getName() { return name; }
public Address getAddressGiven() { return addressGiven; }
enum AddressType { HOME, OFFICE, BUSINESS, DEFAULT }
static class Address {
#JsonProperty("stno") private String streetNo;
#JsonProperty("type") private AddressType addType;
public String getStreetNo() { return streetNo; }
public void setStreetNo(String streetNo) { this.streetNo = streetNo; }
public AddressType getAddType() { return addType; }
public void setAddType(AddressType addType) { this.addType = addType;}
}
public static void main(String[] args) throws JsonProcessingException {
Person person = new Person();
person.name = "joe";
Address address = new Address();
address.addType = AddressType.BUSINESS;
address.streetNo = "010101";
person.addressGiven = address;
ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(person);
System.out.println(body);
}
}
{"name":"joe","addressGiven":{"stno":"010101","type":"BUSINESS"}}
So my entities look like this:
public class HappyClass<T>
{
private String id;
prviate int ver;
private Object obj;
public String getId()
{
return this.id;
}
public void setId( String id )
{
this.id = id;
}
public int getVer()
{
return this.ver;
}
public void setVer( int ver )
{
this.ver = ver;
}
#JsonTypeInfo( use = Id.NONE )
public T getObj()
{
return obj;
}
public void setObj( T obj )
{
this.obj = obj;
}
}
public class HappyGeneric
{
private String someStuff();
public String getSomeStuff()
{
return this.someStuff();
}
public void setSomeStuff( String someStuff )
{
this.someStuff = someStuff;
}
}
If I instantiate a class like this:
HappyClass<HappyGeneric> hc = new HappyClass<HappyGeneric>();
If I send it to Spring in a #ResponseBody it returns this:
{
"id" : "iamsomeid",
"ver" : 123,
"obj" : {
"someStuff" : "iamsomestuff"
}
}
However, when Spring and/or Jackson attempts to unmarshal the same JSON, it figures out that the main class is a HappyClass, however, the getObj() it unmarshals to a LinkedHashMap and not a HappyGeneric no matter what I seem to annotate it with.
Anybody have any ideas how I can force Jackson to unmarshal that generic to the original class?
Thanks!
EDIT: I'm aware I can call mapper.convertValue( blah.getObj(), HappyGeneric.class ) and get the object out that way-- I was hoping to get Spring to figure it out automatically (through annotations, for example).
I have some JSON where one of the keys has one of three values: an int, a string, or a json object. Using the snippet below I can map this field when it is an int or a string but fail when it's a json object. Where am I going wrong? What should I be doing?
The JSON value key looks like:
"value": 51,
or
"value": 51,
or (and this is where I am having trouble)
"value": {"lat": 53.990614999999998, "lng": -1.5391117000000301, "addr": "Harrogate, North Yorkshire, UK"}
public class Test {
public Test() {
}
public static class Value {
public int slidervalue;
public String voicevalue;
public GeoValue geovalue; // problem
public Value(int value) {
this.slidervalue = value
}
public Value(String value) {
this.voicevalue = value;
}
public Value(JSONObject value) {
JSONObject foo = value; // this is never reached
this.geovalue = value; // and how would this work so as map value to a GeoValue?
}
private static class GeoValue {
private double _lat;
private double _lng;
private String _addr;
public float getLat() {
return (float)_lat;
}
public void setLat(float lat) {
_lat = (double)lat;
}
public float getLng() { return (float)_lng;}
public void setLng(float lng) { _lng = (double)lng; }
public String getAddr() { return _addr;}
public void setAddr(String addr) { _addr = addr; }
}
} // end of Value class
public Value getValue() { return _value;}
public void setValue(Value value) {
_value = value;
}
} //end of Test class
and this is being used like this:
ObjectMapper mapper = new ObjectMapper();
instance = mInstances.getJSONObject(i).toString();
Test testinstance = mapper.readValue(instance, Test.class);
public class Test {
public Test() {
}
public static class Value {
public int slidervalue;
public String voicevalue;
public GeoValue geovalue; // problem
public Value(int value) {
this.slidervalue = value
}
public Value(String value) {
this.voicevalue = value;
}
public Value(JSONObject value) {
JSONObject foo = value; // this is never reached
this.geovalue = value; // and how would this work so as map value to a GeoValue?
}
private static class GeoValue {
private double _lat;
private double _lng;
private String _addr;
public float getLat() {
return (float)_lat;
}
public void setLat(float lat) {
_lat = (double)lat;
}
public float getLng() { return (float)_lng;}
public void setLng(float lng) { _lng = (double)lng; }
public String getAddr() { return _addr;}
public void setAddr(String addr) { _addr = addr; }
}
} // end of Value class
public Value getValue() { return _value;}
public void setValue(Value value) {
_value = value;
}
} //end of Test class
and this is being used like this:
ObjectMapper mapper = new ObjectMapper();
instance = mInstances.getJSONObject(i).toString();
Test testinstance = mapper.readValue(instance, Test.class);
This fails with a JSONMappingException: No suitable contructor found for type ... 'value'
Thanks. Alex
What might work is that you mark the constructor that takes JSONObject with #JsonCreator, but do NOT add #JsonProperty for the single parameter. In that case, incoming JSON is bound to type of that parameter (in this case JSONObject, but you could use Map as well), and passed to constructor.
Overloading still works because of special handling for single-string/int/long-argument constructor.
I am not sure if that is the cleanest solution; it might be cleanest to just implement custom deserializer. But it should work.
If your code is what you want, your json should be like this:
{"value":{"slidervalue":1,"voicevalue":"aa","geovalue":{"lat":53.990615,"lng":-1.53911170000003,"addr":"Harrogate, North Yorkshire, UK"}}}
I have a simple jersey web service and I'd like to consume / produce objects that contain map fields, like
#XmlElement
private Map<String,String> properties;
if this string goes into the web service,
{ properties: { key1: val1, key2: val2 )}
the properties field is deserialized as null with no errors. the same JSON goes in and out of GSON no problems, and in the short term I solved this by having jersey consume produce strings and using GSON to serialize / deserialize the JSON.
any ideas?
One option is to use annotated classes. So for instance a user might be represented by the following data.
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "user")
public class User {
private int uid;
public int user_id;
public String user_name;
public String email;
public URI image_url;
public List<User> friends;
public boolean admin;
public User() {
...
}
public User(final int userid) {
// Find user by id
}
}
If you return the User object as in the following piece of code, then jaxb will automatically serialize the List as a JSON list etc etc....
#GET
#Path("/{userid}")
#Produces("application/json", "application/xml")
public User showUser(#PathParam("userid") final int userid) {
return new User(userid);
}
Jersey uses JAXB for serialization. JAXB can not serialize a Map as there is no XML type for Java type Map. Also, Map is an interface and JAXB does not like interfaces.
If you are using JAXBJackson bridge to marshal, you will run into issue.
You will need to create an adapter like below and annotate your Map property with
#XmlJavaTypeAdapter(MapAdapter.class)
private Map<String,String> properties;
#XmlSeeAlso({ Adapter.class, MapElement.class })
public class MapAdapter<K,V> extends XmlAdapter<Adapter<K,V>, Map<K,V>>{
#Override
public Adapter<K,V> marshal(Map<K,V> map) throws Exception {
if ( map == null )
return null;
return new Adapter<K,V>(map);
}
#Override
public Map<K,V> unmarshal(Adapter<K,V> adapter) throws Exception {
throw new UnsupportedOperationException("Unmarshalling a list into a map is not supported");
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="Adapter", namespace="MapAdapter")
public static final class Adapter<K,V>{
List<MapElement<K,V>> item;
public Adapter(){}
public Adapter(Map<K,V> map){
item = new ArrayList<MapElement<K,V>>(map.size());
for (Map.Entry<K, V> entry : map.entrySet()) {
item.add(new MapElement<K,V>(entry));
}
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="MapElement", namespace="MapAdapter")
public static final class MapElement<K,V>{
#XmlAnyElement
private K key;
#XmlAnyElement
private V value;
public MapElement(){};
public MapElement(K key, V value){
this.key = key;
this.value = value;
}
public MapElement(Map.Entry<K, V> entry){
key = entry.getKey();
value = entry.getValue();
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}