Today I discovered some strange behavior Gson. Let's assume we have the following simple JSON
{
"a": {
"foo": 123
}
}
With the corresponding classes representation
class A(val foo: Int)
class B(val a: A) {
val foo = a.foo
}
However, notice, that there is a 'shortcut' for A's foo in B. And the problem is that during GSON deserialization, this field is not initialized. Meaning assertEquals(123, b.foo) fails.
Firstly I looked at the generated bytecode, decompiled it, but it looks fine:
public static final class A {
private final int foo;
public final int getFoo() {
return this.foo;
}
public A(int foo) {
this.foo = foo;
}
}
public static final class B {
private final int foo;
#NotNull
private final SubscriptionChangeTest.A a;
public final int getFoo() {
return this.foo;
}
#NotNull
public final SubscriptionChangeTest.A getA() {
return this.a;
}
public B(#NotNull SubscriptionChangeTest.A a) {
Intrinsics.checkNotNullParameter(a, "a");
super();
this.a = a;
this.foo = this.a.getFoo();
}
}
The only reason I see is that GSON does not invoke constructor, but rather fills those final fields using reflection after the object is created. I will be grateful if you will share your thoughts about it. Thanks in advance.
P.S They question is not how to fix it, it is simple (changing B.foo to getter property), but rather why it works the way it does
It turns out, that indeed Gson does not invoke constructors, but rather create objects using sun.misc.unsafe.
Related
Serialization does not happen properly when I use #Json in the fields but it started working after changing to #field:Json.
I came through this change after reading some bug thread and I think this is specific to kotlin. I would like to know what difference does #field:Json bring and is it really specific to kotlin?
Whatever you put between # and : in your annotation specifies the exact target for your Annotation.
When using Kotlin with JVM there is a substantial number of things generated, therefore your Annotation could be put in many places. If you don't specify a target you're letting the Kotlin compiler choose where the Annotation should be put. When you specify the target -> you're in charge.
To better see the difference you should inspect the decompiled Java code of the Kotlin Bytecode in IntelliJ/Android Studio.
Example kotlin code:
class Example {
#ExampleAnnotation
val a: String = TODO()
#get:ExampleAnnotation
val b: String = TODO()
#field:ExampleAnnotation
val c: String = TODO()
}
Decompiled Java code:
public final class Example {
#NotNull
private final String a;
#NotNull
private final String b;
#ExampleAnnotation
#NotNull
private final String c;
/** #deprecated */
// $FF: synthetic method
#ExampleAnnotation
public static void a$annotations() {
}
#NotNull
public final String getA() {
return this.a;
}
#ExampleAnnotation
#NotNull
public final String getB() {
return this.b;
}
#NotNull
public final String getC() {
return this.c;
}
public Example() {
boolean var1 = false;
throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
}
}
For more info go to Kotlin docs.
I have a Controller, an Entity, and a Validator for my project.
The controller looks vaguely like this:
#RequestMapping("foo")
#Controller
public class RequestMapper {
#RequestMapping("jsonCreate")
#ResponseBody
public String createInfo(#Valid #ModelAttribute("node") String nodeJson) throws JsonParseException, JsonMappingException, IOException {
Foo obj = new ObjectMapper().readValue(nodeJson, Foo.class);
///missing validation
fooDao.save(obj);
}
}
#Entity
public class Foo {
#NotNull
private String bar;
///getters, setters, constructors omitted
}
public class FooValidator {
#NotEmpty(message = "Bar cannot be null")
#Size(min = 2, message = "Bar must be a minimum of 2 characters long")
private String bar;
////getters, setters and constructors omitted
}
So, when I am trying to call the createInfo method with the JSON:
{ "bar" : "a" }
I do not get any of the validation that I do in a form (not unexpected, but still a bug to get fixed).
Could someone point me to what needs to happen to get the FooValidator to properly examine the Foo object?
I tried putting this in FooValidator:
public<T> List<String> validate (T input) {
List<String> errors = new ArrayList<>();
Set<ConstraintViolation<T>> violations = Validation.buildDefaultValidatorFactory().getValidator().validate(input);
if (violations.size() > 0) {
for (ConstraintViolation<T> violation : violations) {
errors.add(violation.getPropertyPath() + " " + violation.getMessage());
}
}
}
And then calling
new FooValidator().validate(foo);
but nothing happened in regards to being at least 2 characters long.
You should put FooValidator after #Valid then use ModelMapper to convert FooValidator to Foo
#Autowired
ModelMapper modelMapper;
public String createInfo(#Valid #ModelAttribute("node") FooValidator fooValidator) {
Foo foo = modelMapper.map(fooValidator, Foo.class)
fooDao.save(foo);
}
I've read countless articles about parsing Java objects to JSONs and still have issues...
I know that there are a bunch of frameworks out there and this is where things messed up I guess.
I'm trying to parse a map into a json:
Map<CategoryBean, Double> questionsPercentagePerCategory;
here's how CategoryBean looks like:
#XmlRootElement
public class CategoryBean implements Serializable{
private static final long serialVersionUID = -7306680546426636719L;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
questionsPercentagePerCategory is a variable inside a wrapper json called: PrePracticeBean
and this is how it looks:
#XmlRootElement
public class PrePracticeBean implements Serializable {
private int maxQuestionsAllowedForUser;
private int maxQuestionsAllowedForUserAfterCreditOver;
private int questionsInExam;
#XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
private Map<CategoryBean, Double> questionsPercentagePerCategory;
private static final long serialVersionUID = -655358519739911024L;
public int getMaxQuestionsAllowedForUser() {
return maxQuestionsAllowedForUser;
}
public void setMaxQuestionsAllowedForUser(int maxQuestionsAllowedForUser) {
this.maxQuestionsAllowedForUser = maxQuestionsAllowedForUser;
}
public int getMaxQuestionsAllowedForUserAfterCreditOver() {
return maxQuestionsAllowedForUserAfterCreditOver;
}
public void setMaxQuestionsAllowedForUserAfterCreditOver(int maxQuestionsAllowedForUserAfterCreditOver) {
this.maxQuestionsAllowedForUserAfterCreditOver = maxQuestionsAllowedForUserAfterCreditOver;
}
public int getQuestionsInExam() {
return questionsInExam;
}
public void setQuestionsInExam(int questionsInExam) {
this.questionsInExam = questionsInExam;
}
public Map<CategoryBean, Double> getQuestionsPercentagePerCategory() {
return questionsPercentagePerCategory;
}
public void setQuestionsPercentagePerCategory(Map<CategoryBean, Double> questionsPercentagePerCategory) {
this.questionsPercentagePerCategory = questionsPercentagePerCategory;
}
}
as you can see I've marked both beans with #XmlRootElement annotation to get Jeresey's OOB bean to JSON parsing functionality as specified here
Furthermore, here's how the XMLGenericMapAdapter looks like:
public class XmlGenericMapAdapter<K, V> extends XmlAdapter<MapType<K, V>, Map<K, V>> {
#Override
public Map<K, V> unmarshal(MapType<K, V> orgMap) throws Exception {
HashMap<K, V> map = new HashMap<K, V>();
for (MapEntryType<K, V> mapEntryType : orgMap.getEntries()) {
map.put(mapEntryType.getKey(), mapEntryType.getValue());
}
return map;
}
#Override
public MapType<K, V> marshal(Map<K, V> v) throws Exception {
MapType<K, V> mapType = new MapType<K, V>();
for (Map.Entry<K, V> entry : v.entrySet()) {
MapEntryType<K, V> mapEntryType = new MapEntryType<K, V>();
mapEntryType.setKey(entry.getKey());
mapEntryType.setValue(entry.getValue());
mapType.getEntries().add(mapEntryType);
}
return mapType;
}
}
Well, the end result is what makes me crazy... it's intermittent... when running the code in debug mode, this works flawlessly showing a nested json for the map and each key:value pair is another nested json. However, when invoked in run mode, I get an ugly "memory address" instead of the CategoryBean key...
My only guess is that this is related to class loading matters and that I might be having some other JAR that's having a class which is loaded first in debug mode but not in run mode...
anyways, any suggestions as to how this should be done, would be appreciated.
thanks,
GBa.
Well, solved...
some JAXB/Jersey stuff which I'm not into understanding up to the last bit... but I bet that there's some JAXB guru out there who could give the right explanation why this is the case...
Anyways, bottom line is that the CategoryBean class should not have the annotation #XmlRootElement but instead should have #XmlAccessorType(XmlAccessType.FIELD)
I got the inspiration for that from Serializer for (Hash)Maps for Jersey use?
thanks,
GBa.
Currently I am trying to create a webservice which simply returns a list;
#Path("/random")
#Singleton
public class Random
{
#GET
#Path("/")
#Produces(MediaType.APPLICATION_JSON)
public MyResult<String> test()
{
MyResult<String> test = new MyResult<String>();
test.add("Awesome");
return test;
}
}
And my MyResult class looks like this:
#XmlRootElement
public class MyResult<T> implements Iterable<T>
{
private ArrayList<T> _items;
private int _total;
public MyResult()
{
_items = new ArrayList<T>();
}
public ArrayList<T> getItems()
{
return _items;
}
public void setItems(ArrayList<T> items)
{
_items = items;
}
public int getTotal()
{
return _total;
}
public void setTotal(int total)
{
_total = total;
}
public void add(T item)
{
getItems().add(item);
}
public Iterator<T> iterator()
{
return getItems().iterator();
}
}
Now I get the following result from the service:
{"items":[{"#type":"xs:string","$":"Awesome"}],"total":"0"}
But I don't want any of this information, I just require this:
{"items":["Awesome"],"total":"0"}
It seems to me this requires some configuration somewhere, who know how to get the required result?
Assuming you are using jackson, take a look at #JsonTypeInfo annotation. It is used for configuring details of if and how type information is used with JSON serialization and deserialization. The use and behaviour of it would depend on the version of jackson you are using.
To completely suppress type information, I had to use the following annotations:
#JsonTypeInfo(use=JsonTypeInfo.Id.NONE)
#JsonDeserialize(as=NoType.class)
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.