I want to be able to modify a java object from a JSON string without having to specify every field. Example:
package controller.test;
import com.google.gson.Gson;
public class Tests {
public Tests() {
test();
}
private void test() {
Person realPerson = new Person();
realPerson.setName("Bobby");
realPerson.setAge(28);
// Now trying to set the name from a JSON string
Gson gson = new Gson();
Person modifiedPerson = gson.fromJson("{\"name\":\"Justin\"}", Person.class);
System.out.println(gson.toJson(realPerson));
// prints: {"name":"Bobby","age":28}
System.out.println(gson.toJson(modifiedPerson));
// prints: {"name":"Justin","age":0}
}
public static void main(String[] args) {
new Tests();
}
}
class Person {
private String name;
private int age;
public Person() {
}
// Getters & setters are added here
}
How can I modify realPerson.name from "Bobby" to "Justin" without having to specify the age?
For a small class like this example it's not really a big problem: I could just check and set each field individually. But my class contains 138 (!) different fields.
EDIT:
My question was maybe poorly explained, let me sum up my problem:
I have an instance of a class (with ~130 different fields that are all set to individual values).
I want to be able to modify values in this instance using a JSON string.
Example JSON string: {"field13": "5", "field100": "2"}
Is there any way to accomplish this without having to write specific code for each field in my java class?
If it is an option to use Jackson as deserializer you could use the Annotations in the dataholder:
#JsonAnySetter
public void set(String name, Object value);
#JsonAnyGetter
public Map<String, Object> get();
Related
I have a structure with circular references.
And for debug purposes, I want to dump it. Basically as any format, but I chose JSON.
Since it can be any class, I chose GSON which doesn't needs JAXB annotations.
But GSON hits the circular references and recurses until StackOverflowError.
How can I limit GSON to
ignore certain class members?
Both #XmlTransient and #JsonIgnore are not obeyed.
ignore certain object graph paths? E.g. I could instruct GSON not to serialize release.customFields.product.
go to the depth of at most 2 levels?
Related: Gson.toJson gives StackOverFlowError, how to get proper json in this case? (public static class)
Simply make the fields transient (as in private transient int field = 4;). GSON understands that.
Edit
No need for a built-in annotation; Gson lets you plug in your own strategies for excluding fields and classes. They cannot be based on a path or nesting level, but annotations and names are fine.
If I wanted to skip fields that are named "lastName" on class "my.model.Person", I could write an exclusion strategy like this:
class MyExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipField(FieldAttributes fa) {
String className = fa.getDeclaringClass().getName();
String fieldName = fa.getName();
return
className.equals("my.model.Person")
&& fieldName.equals("lastName");
}
#Override
public boolean shouldSkipClass(Class<?> type) {
// never skips any class
return false;
}
}
I could also make my own annotation:
#Retention(RetentionPolicy.RUNTIME)
public #interface GsonRepellent {
}
And rewrite the shouldSkipField method as:
public boolean shouldSkipField(FieldAttributes fa) {
return fa.getAnnotation(GsonRepellent.class) != null;
}
This would enable me to do things like:
public class Person {
#GsonRepellent
private String lastName = "Troscianko";
// ...
To use a custom ExclusionStrategy, build Gson object using the builder:
Gson g = new GsonBuilder()
.setExclusionStrategies(new MyOwnExclusionStrategy())
.create();
I know this question has a few years now, but I'd like to contribute with my solution.
Although #fdreger's answer is completely valid in case you want to exclude a field always, it doesn't work if you want to exclude it just in certain cases, avoiding this way the recursion.
The way I approached the problem is:
I write my own JsonSerializer. In it, I define a static variable to control de number of times an object of this same class is serialize and depending on the value, the object can be serialized or not.
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class UserJsonSerializer extends JsonSerializer<User> {
private static final ThreadLocal<Integer> depth = new ThreadLocal<Integer>() {
#Override
protected Integer initialValue() {
return 0;
}
};
#Override
public void serialize(User user, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
// Here, we limit the number of instances to return. In this case, just 1.
depth.set(depth.get() + 1);
if(depth.get() >= 1) {
generator.writeNull();
} else {
generator.writeObject(user);
}
}
public static void clear() {
depth.remove();
}
}
Bind the UserJsonSerializer to the class you want to control
public class SomeClass implements Serializable {
#JsonSerialize(using = UserJsonSerializer.class)
private User user;
//...others fields...
}
Don't forget to call UserJsonSerializer#clear() method to reinitialize the counter everytime you're going to parse a new entity.
I hope this helps.
I have the following POJO using Immutables+Jackson under the hood:
#JsonInclude(JsonInclude.Include.NON_NULL)
abstract class AbstractQueryRequest {
#JsonProperty("reqid")
public abstract String reqid();
#JsonProperty("rawquery")
public abstract String rawquery();
}
At some point I need to build another object based on the fields of the POJO, something along this line:
final HttpUrl.Builder urlBuilder = HttpUrl.parse(cfg.baseUrl()).newBuilder();
urlBuilder.addQueryParameter("reqid", request.reqid())
.addQueryParameter("rawquery", request.rawquery());
It's quite annoying to keep the POJO and this call aligned upon changes, I was wondering if it was possible to access programmatically each JsonProperty instead of typing the string manually.
Note that it is fine to write the getters by hand as I can easily refactor and I have the compiler double checking, but for strings I am worried for people down the line and I would like to "read" them from the POJO class somehow.
You can do it via reflection. You need to take method annotation values which annotated with JsonProperty. But I recommend you to use JsonProperty on fields, not methods.
Here is an example for your current requirement :
public class Main {
public static void main(String[] args) {
AbstractQueryRequest someType = new SomeType();
for(Method method : x.getClass().getSuperclass().getDeclaredMethods()) {
if (method.isAnnotationPresent(JsonProperty.class)) {
JsonProperty annotation = method.getAnnotation(JsonProperty.class);
System.out.println(annotation.value());
}
}
}
}
class SomeType extends AbstractQueryRequest {
#Override
public String reqid() {
return null;
}
#Override
public String rawquery() {
return null;
}
}
Output is :
rawquery
reqid
I was trying to learn the concept of inheritance and deserialization of java beans through Gson framework. Details regarding java bean classes and json files are given below.
ParentBean.java
public class ParentBean {
protected String key1;
protected String key2;
public ParentBean(String key1, String key2) {
super();
this.key1 = key1;
this.key2 = key2;
}
}
Bean1.java
public class Bean1 extends ParentBean {
private String key3;
public Bean1(String key1, String key2, String key3) {
super(key1, key2);
this.key3 = key3;
}
}
Bean2.java
public class Bean2 extends ParentBean {
private String key4;
public Bean2(String key1, String key2, String key4) {
super(key1, key2);
this.key4 = key4;
}
}
bean1.json
{
"key1":"value1",
"key2":"value2",
"key3":"value33"
}
bean2.json
{
"key1":"value1",
"key2":"value2",
"key4":"value43"
}
To explore things about inheritance and deserialization, I have used the following code:
Usage.java
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
public class Usage {
public static void main(String[] args) throws FileNotFoundException {
RuntimeTypeAdapterFactory<ParentBean> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(ParentBean.class, "type")
.registerSubtype(Bean1.class, "bean1")
.registerSubtype(Bean2.class, "bean2");
Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();
FileReader fr = new FileReader("bean1.json");
Type pType = new TypeToken<ParentBean>(){}.getType();
ParentBean pb = gson.fromJson(fr, pType);
if (pb instanceof Bean1) {
System.out.println(" Bean1");
} else if (pb instanceof Bean2) {
System.out.println("Bean2");
}
}
}
I am getting an error stack which is as follows:
Exception in thread "main" com.google.gson.JsonParseException: cannot deserialize class inheritance.ParentBean because it does not define a field named type
at com.google.gson.typeadapters.RuntimeTypeAdapterFactory$1.read(RuntimeTypeAdapterFactory.java:205)
at com.google.gson.TypeAdapter$1.read(TypeAdapter.java:199)
at com.google.gson.Gson.fromJson(Gson.java:795)
at com.google.gson.Gson.fromJson(Gson.java:761)
at inheritance.Usage.main(Usage.java:23)
In search of finding solution, I came across this stack overflow discussion. Unfortunately the discussion was about create() method. Error stack says the problem was with line 23 and this line contains fromJson() method.
You need to tell gson more about the types. When serializing also the type needs to be serialized. So as the first comment by Jacob G. suggests you need the type field:
Docs for RuntimeTypeAdapterFactory.of(Class<T> baseType, String typeFieldName) states:
Creates a new runtime type adapter using for baseType using typeFieldName as the type field name. Type field names are case sensitive.
Add it to your ParentBean:
// Init it for serializing
// You used values like 'bean1' & 'bean2' but using class name is more generic
protected String type = getClass().getName();
According to above changes in bean type names change the building of RuntimeTypeAdapterFactory accordingly:
RuntimeTypeAdapterFactory<ParentBean> runtimeTypeAdapterFactory =
RuntimeTypeAdapterFactory
.of(ParentBean.class, "type") // typeFieldName
.registerSubtype(Bean1.class, Bean1.class.getName())
.registerSubtype(Bean2.class, Bean2.class.getName());
Finally - when de-serailizing - the Json files need also the type information which will be serialized from the field type so add it also fro both beans Json with correct package name:
"type":"org.example.gson.runtime.Bean1",
and
"type":"org.example.gson.runtime.Bean2",
You do not need to explicitly add a type field to your beans. As pirho suggests, you need to have the type field in your json string. If you're crafting this by hand, just add the type field, e.g.
{
"type":"bean1",
"key1":"value1",
"key2":"value2",
"key3":"value33"
}
Presumably, you're also serializing your objects and to achieve that, you'll need to specify the base class during serialization.
As swankjesse points out in
https://github.com/google/gson/issues/712
Try replacing this:
final String jsonStr = mGson.toJson(new Child());
With this:
final String jsonStr = mGson.toJson(new Child(), Base.class);
Then, when you serialize your types, your output json will be
{
"type":"bean1",
"key1":"value1",
"key2":"value2",
"key3":"value33"
}
That way, your beans can stay pure, without knowing that they are providing some type key to be used in serialization.
I'm serializing some existing objects with Jackson 2.22, leveragin the MixIn feature to decouple the real object from the Jackson annotations configuration.
Actually my mixin is an interface that declares the same methods of the target class and annotates them, here's an example.
Target class:
public class Product {
// ...
public String getName();
public String getDescription();
public String getPrice();
public String getFinalPrice();
public String getDiscount();
// ...
}
and the mixin:
public interface ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
}
My JSON should have some more informations, computed from several methods or fields of the target class.
Is this even possible in Jackson?
I tried turning the mixin in a class and adding a new method there, but that didn't work.
public class ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
#JsonProperty("images")
public List<String> getImages() { /* ... */ }
}
I guess this is because the mixin only provides annotations for the target class, but is the latter that is read for serialization.
Of course, if I change the object to be serialized with a new subclass that contains the new method I need, that works, but the objects come from our services layers, and this would mean I have to rewrite all those methods.
I'm using Jackson with Jersey, so don't want to change Jackson with another library.
Here's how I did it.
The solution is to specify a custom JsonSerializer implementation to the field getter.
First of all, I changed the mixin interface to a class that extends the entity (target) class, so that it can access the target class data.
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
}
Next, I implemented the JsonSerializer that would create the derived property I want:
public static class ImagesSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
Product p = (Product) jgen.getCurrentValue();
int num = p.getNumberOfImages();
List<String> imgs = new ArrayList<String>(num);
for(int i = 0; i < num; i++) {
String src = "/include/images/showImage.jsp?"+"id="+p.getId()+"&number="+i;
imgs.add(src);
}
provider.defaultSerializeValue(imgs, jgen);
}
}
This is a really simple implementation, more safety checks should be done.
What this does is, basically, retrieve the whole entity instance from the JSON generator, build up a custom object and then ask Jackson to serialize it.
I implemented it inside my ProductApi as a static class, but just for simplicity.
Finally, the serializer needs to be bound to the JsonProperty annotated field:
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
#JsonSerialize(using=ImagesSerializer.class)
#JsonProperty("images")
#Override
public String getImage() { // in my entity this returns an image number, whereas in my JSON I want a list of URLs
return "";
}
// ...
}
As a side note, it seems that the returned value of the getImage() method is not used.
Why don't you just make some fields, which should be serialized and use Gson for it?
I want to understand why it is possible to create and fill an object that only got private variables and an overwritten constructor.
Code example:
public class Test {
public static void main(String[] args) {
String json = "{\"id\":\"123546\"}";
Gson gson = new Gson();
Participant p = gson.fromJson(json, Participant.class);
System.out.println(p.getId());
}
}
public class Participant {
private int id;
public Participant() {
}
public int getId() {
return id;
}
}
It prints "123546" correctly.
The gson.fromJson Method has following signature: <T> T: fromJson(String json, Class<T> classOfT)
http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/index.html
Gson, like many other JSON parser/generator libraries, uses reflection to populate fields, either directly or through methods.
Through reflection you can access public and non-public members of a class and modify them (fields) or invoke them (methods and constructors).
Your starting point should be the Class class, which provides methods to retrieve the Fields, Methods, and Constructor's of a class.
Gson uses the Class object you provide, Participant.class, to find out all the fields it needs to populate. It parses the JSON and (attempts to) maps them, one by one.