I have a little problem.
I stored some data in a json-file, and it works fine.
But if i try to retrieval this data there is a problem with the interfaces of the object i am trying to create.
This is a simplified version of my class
public class MyObject{
//this is a interface
public Vector3Int position;
...}
Ok, GSON can't create interfaces without a consturctor, so i create this class:
public class VectorInstanceCreator implements InstanceCreator<Vector3Int >{
#Override
public Vector3Int createInstance(Type arg0) {
return new VectorImpl();
}
}
And i use it in this way:
...
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Vector3Int.class, new new VectorInstanceCreator());
Gson gson = gsonBuilder.create();
gson.fromJson(myJsonString, MyObject.class);
...
So, it works but the values in my json file is for example:
"position": {"x": 1.0, "y": 1.0,"z": 0.0}
but each value in the Java-Object-Vector is 0. So it means, gson use just the given consturctor of my VectorInstanceCreator class, but is not setting the values of the JSON-File/Object.
How do i set this values by using a interface with InstanceCreator?
I found a simple solution on http://technology.finra.org/code/serialize-deserialize-interfaces-in-java.html for my problem. With the template InterfaceAdapter, each class name will stored additional in the json file. So the retrieval method, to restore the classes know the exact class and not just the interface. This works well for me.
Related
I have a POJO class like:
class Cat {
public Cat() {}
private String name;
private JsonElement arbitraryProperties;
}
I am using Jackson (JAXB) and cat is a resource I want to import and export.
GET /cats/{id}
POST /cats
Export is working fine. Import did not work because JsonElement is abstract. So I added #JsonDeserialize(using = MyJsonElementDeserialize.class) annotation to the arbitraryProperties field.
The MyJsonElementDeserialize class looks like:
public class JsonElementDeserialize extends JsonDeserializer<JsonElement> {
public JsonElementDeserialize() {
}
#Override
public JsonElement deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return new JsonObject(); // here I need to create my object later
}
}
When I now call POST /cats with payload:
{
"name": "Mia",
"arbitraryProperties": {
"a": 3,
"b": [],
"c": {
"d": "race"
}
}
}
Jacksons returns error code 400:
Unrecognized field "a" (class Cat), not marked as ignorable (1 known properties: "name"]).
Where is my mistake? How can I have a JsonElement in my POJO that gets automatically serialized and deserialized?
Here is a simple solution using a setter method in Cat to transform something Jackson parses (e.g. Map<String, Object>) to a GSON JsonElement type:
void setArbitraryProperties(Map<String, Object> properties) {
arbitraryProperties = new Gson().toJsonTree(properties).getAsJsonObject();
}
You can do something similar inside a custom deserializer if you can't use a setter here. I find setters/getters simpler for transformations with Jackson.
Note that a Gson instance is employed to convert the Map to JsonElement (a JsonObject actually). Docs for toJsonTree.
Since you have a field of a type defined in GSON and therefore you have GSON as a dependency and inexorably need GSON to create this field, you may consider Sharon's suggestion and skip Jackson and use GSON for deserialization.
The problem here is that deserializers must consume all content that they cover: that is, if your deserializer does not "read" JSON content that would bind (contents of a here), they are left for further processing.
Solution should be easy, just call
jsonParser.skipChildren();
in deserialize method and it will skip over current input token, as well as all of its contents (if its JSON Object or Array).
Alternatively, if you wanted to use contents, you could use
jsonParser.readValueAsTree();
and there are a few other convenience methods.
If could, I would change JsonElement to JsonNode, and use “Tree Model” of Jackson.
Try to use "produces=MediaType.APPLICATION_JSON_VALUE" and #ResponseBody in controller method if you are using Spring mvc.
Is there any way the set methods of a given class, are used when using Gson's fromJson method?
I would like to do this because for every String global variable of the target class a trim is made.
Is there any GSON API annotation for this?
I am aware that GSON provides the ability to write custom serializers/deserializers but I would like to know if there is another way to achieve this.
No, there is not. Gson works mainly by reflection on instance fields. So if you do not plan to move to Jackson that has this feature I think you cannot have a general way to call your setters. So there's no annotation for that.
BUT
to achieve your specific need you could:
write your own custom TypeAdapter or
create a constructor that has the string you intend to trim and create a custom InstanceCreator or
parse your JSON as JsonObject, do some processing of the strings and then use that object as source for parsing into your class.
I can provide you with more hints as long as you post some code or give information about your data/JSON.
I implemented a JsonDeserializer<String> and registered it on GsonBuilder. So, to all String fields received, Gson will use my StringGsonTypeAdapter to deserialize the value.
Below is my code:
import static net.hugonardo.java.commons.text.StringUtils.normalizeSpace;
import static net.hugonardo.java.commons.text.StringUtils.trimToNull;
final class StringGsonTypeAdapter implements JsonDeserializer<String> {
private static final StringGsonTypeAdapter INSTANCE = new StringGsonTypeAdapter();
static StringGsonTypeAdapter instance() {
return INSTANCE;
}
#Override
public String deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return normalizeSpace(trimToNull(jsonElement.getAsString()));
}
}
...and my GsonBuilder:
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, StringGsonTypeAdapter.instance())
.create())
I want to deserialize json objects to specific types of objects (using Gson library) based on type field value, eg.:
[
{
"type": "type1",
"id": "131481204101",
"url": "http://something.com",
"name": "BLAH BLAH",
"icon": "SOME_STRING",
"price": "FREE",
"backgroundUrl": "SOME_STRING"
},
{
....
}
]
So type field will have different (but known) values. Based on that value I need to deserialize that json object to appropriate model object, eg.: Type1Model, Type2Model etc.
I know I can easily do that before deserialization by converting it to JSONArray, iterate through it and resolve which type it should be deserialized to. But I think it's ugly approach and I'm looking for better way. Any suggestions?
You may implement a JsonDeserializer and use it while parsing your Json value to a Java instance. I'll try to show it with a code which is going to give you the idea:
1) Define your custom JsonDeserializer class which creates different instance of classes by incoming json value's id property:
class MyTypeModelDeserializer implements JsonDeserializer<MyBaseTypeModel> {
#Override
public MyBaseTypeModel deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement jsonType = jsonObject.get("type");
String type = jsonType.getAsString();
MyBaseTypeModel typeModel = null;
if("type1".equals(type)) {
typeModel = new Type1Model();
} else if("type2".equals(type)) {
typeModel = new Type2Model();
}
// TODO : set properties of type model
return typeModel;
}
}
2) Define a base class for your different instance of java objects:
class MyBaseTypeModel {
private String type;
// TODO : add other shared fields here
}
3) Define your different instance of java objects' classes which extend your base class:
class Type1Model extends MyBaseTypeModel {
// TODO: add specific fields for this class
}
class Type2Model extends MyBaseTypeModel {
// TODO: add specific fields for this class
}
4) Use these classes while parsing your json value to a bean:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyBaseTypeModel.class, new MyTypeModelDeserializer());
Gson gson = gsonBuilder.create();
MyBaseTypeModel myTypeModel = gson.fromJson(myJsonString, MyBaseTypeModel.class);
I can not test it right now but I hope you get the idea. Also this link would be very helpful.
#stephane-k 's answer works, but it is a bit confusing and could be improved upon (see comments to his answer)
Copy https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java into your project. (It's ok; these classes are designed to be copy/pasted https://github.com/google/gson/issues/845#issuecomment-217231315)
Setup model inheritance:
// abstract is optional
abstract class BaseClass {
}
class Type1Model extends BaseClass {
}
class Type2Model extends BaseClass {
}
Setup GSON or update existing GSON:
RuntimeTypeAdapterFactory<BaseClass> typeAdapterFactory = RuntimeTypeAdapterFactory
.of(BaseClass.class, "type")
.registerSubtype(Type1Model.class, "type1")
.registerSubtype(Type2Model.class, "type2");
Gson gson = new GsonBuilder().registerTypeAdapterFactory(typeAdapterFactory)
.create();
Deserialize your JSON into base class:
String jsonString = ...
BaseClass baseInstance = gson.fromJson(jsonString, BaseClass.class);
baseInstance will be instanceof either Type1Model or Type2Model.
From here you can either code to an interface or check instanceof and cast.
use https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
then configure it with
public static final class JsonAdapterFactory extends
RuntimeTypeAdapterFactory<MediumSummaryInfo> {
public JsonAdapterFactory() {
super(MyBaseType.class, "type");
registerSubtype(MySubtype1.class, "type1");
registerSubtype(MySubtype2.class, "type2");
}
}
and add the annotation:
#JsonAdapter(MyBaseType.JsonAdapterFactory.class)
to MyBaseType
Much better.
If you have a lot of sub types and you do not want to or cannot maintain a list of them, you can also use an annotation based approach.
Here is the required code and also some usage examples:
https://gist.github.com/LostMekka/d90ade1fe051732d6b4ac60deea4f9c2
(it is Kotlin, but can easily be ported to Java)
For me, this approach is especially appealing, since I write a small library that does not know all possible sub types at compile time.
If I have a type "Person", and it has multiple fields, including "password", then how do I tell GSON that I want accept the password field when it's passed in, but not to pass it back out?
Specifically, in this case, it's because my web front end can be used to update the password and send it to the Java side,, but I never want to send the password back to the front end (for obvious security reasons).
I am not sure you can do it with Gson, but you could with Genson. Put #JsonIgnore(deseriaize=true) on your getPassword method.
Or if you want genson to use only fields instead of public getter/setter and fields, configure it like that:
Genson genson = new Genson.Builder()
.setUseGettersAndSetters(false)
.setFieldVisibility(VisibilityFilter.DEFAULT)
.create();
In that case put the annotation on the field.
You can deserialize your class as usual (since you want to deserialize all the fields) and write a custom serializer that excludes the password. Something like this:
public class PersonSerializer implements JsonSerializer<Person> {
#Override
public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject obj = new JsonObject();
obj.addProperty("name", src.name);
obj.addProperty("gender", src.gender);
obj.addProperty("age", src.age);
//And all the other fields but the password...
return obj;
}
}
Then you just need to register the serializer with:
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(Person.class, new PersonSerializer());
And finally serialize your object as usual with gson.toJson method...
I'm not sure if it's the best approach, but it's pretty straightforward... Otherwise you can take a look at Gson's excusion strategies...
I have an object like:
public class Foo{
public String f1="[{\"jsonField\":\"something\"},{\"jsonField\":\"something\"}]";
}
Gson would serialize it to:
{"f1":"[{\"jsonField\":\"something\"},{\"jsonField\":\"something\"}]"}
The f1 field was serialized to a string. Apparently, the field is a well formed json-format string. How can I do to serialized the field to an jso array, like below:
{"f1":[{"jsonField":"something"},{"jsonField":"something"}]}
PS. For performance consideration, I can't deserialize then serialize the field.
Gson is serializing your string field to a string. That's normal. If your field, however, was a List<String>, Gson would give you:
{"f1":["jsonField:something","jsonField:something"]}
And if it was a List where something has a field called jsonField you'd get what you actually want.
This is the implicit way that Gson serializes your objects. If you don't like it, you need to implement your own TypeAdapter and register it with Gson at the builder.
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyObject.class, new SomethingGsonAdapter());
Gson gson = builder.create()
And you need to define:
public class SomethingGsonAdapter implements JsonDeserializer<DataItem>, JsonSerializer<DataItem>;