Global property filter in Jackson - json

Is there a way to register a global property filter in ObjectMapper?
Global means that it will be applied to all serialized beans. I can't use annotations (I can't modify serialized beans) and don't know what properties the beans have.
The filtering should be name based.
My first idea was to write a custom serializer, but I don't know what should I pass to the constructor.

I'd make use of a FilterProvider. It's a little involved, but not too unwieldy.
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.annotate.JsonFilter;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Bar bar = new Bar();
bar.id = "42";
bar.name = "James";
bar.color = "blue";
bar.foo = new Foo();
bar.foo.id = "7";
bar.foo.size = "big";
bar.foo.height = "tall";
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
System.out.println(mapper.writeValueAsString(bar));
// output:
// {"id":"42","name":"James","color":"blue","foo":{"id":"7","size":"big","height":"tall"}}
String[] ignorableFieldNames = { "id", "color" };
FilterProvider filters = new SimpleFilterProvider().addFilter("filter properties by name", SimpleBeanPropertyFilter.serializeAllExcept(ignorableFieldNames));
mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
mapper.getSerializationConfig().addMixInAnnotations(Object.class, PropertyFilterMixIn.class);
ObjectWriter writer = mapper.writer(filters);
System.out.println(writer.writeValueAsString(bar));
// output:
// {"name":"James","foo":{"size":"big","height":"tall"}}
}
}
#JsonFilter("filter properties by name")
class PropertyFilterMixIn
{
}
class Bar
{
String id;
String name;
String color;
Foo foo;
}
class Foo
{
String id;
String size;
String height;
}
For other approaches and more information, I recommend the following resources.
http://wiki.fasterxml.com/JacksonJsonViews
http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html
http://wiki.fasterxml.com/JacksonFeatureJsonFilter
http://www.cowtowncoder.com/blog/archives/2011/09/entry_461.html

Related

Show JSON in TableView

I am developing a generic editor for JSON Array using JavaFX.
The display in the table in such a way that the columns will be the keys, and the value in the rows will be more descriptive. There can be a different number of keys in one JSONObject.
JSON of the form:
"[{\"key1\": 1, \"key2\": 2}, {\"key1\": 3, \"key2\": 4}]"
It needs to look like this:
key1
key2
1
2
3
4
Have any suggestions?
This can be broken down into two parts.
Use GSON to parse a JSON Array to an Array of POJOs.
Display a List of Objets in a TableView.
Key Code
//Add data to the TableView!
String jsonString = "[{\"keyOne\":\"1\", \"keyTwo\":\"2\"}, {\"keyOne\":\"3\", \"keyTwo\":\"4\"}]";
Gson gson = new Gson();
Data[] dataList = gson.fromJson(jsonString, Data[].class);
ObservableList<Data> observableList = FXCollections.observableArrayList(dataList);
tableView.setItems(observableList);
Main
import com.google.gson.Gson;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import javafx.scene.layout.StackPane;
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage){
TableView<Data> tableView = new TableView();
TableColumn<Data, String> column1 = new TableColumn<>("Key One");
column1.setCellValueFactory((cdf) -> new SimpleStringProperty(cdf.getValue().getKeyOne()));
TableColumn<Data, String> column2 = new TableColumn<>("Key Two");
column2.setCellValueFactory((cdf) -> new SimpleStringProperty(cdf.getValue().getKeyTwo()));
tableView.getColumns().add(column1);
tableView.getColumns().add(column2);
//Add data to the TableView!
String jsonString = "[{\"keyOne\":\"1\", \"keyTwo\":\"2\"}, {\"keyOne\":\"3\", \"keyTwo\":\"4\"}]";
Gson gson = new Gson();
Data[] dataList = gson.fromJson(jsonString, Data[].class);
ObservableList<Data> observableList = FXCollections.observableArrayList(dataList);
tableView.setItems(observableList);
Scene scene = new Scene(new StackPane(tableView));
stage.setTitle("JavaFX 13");
stage.setScene(scene);
stage.show();
}
}
Data Class
/**
*
* #author sedj601
*/
public class Data {
private String keyOne;
private String keyTwo;
public Data(String keyOne, String keyTwo) {
this.keyOne = keyOne;
this.keyTwo = keyTwo;
}
public String getKeyOne() {
return keyOne;
}
public void setKeyOne(String keyOne) {
this.keyOne = keyOne;
}
public String getKeyTwo() {
return keyTwo;
}
public void setKeyTwo(String keyTwo) {
this.keyTwo = keyTwo;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Data{keyOne=").append(keyOne);
sb.append(", keyTwo=").append(keyTwo);
sb.append('}');
return sb.toString();
}
}
module-info.java
module com.mycompany.javafx_test_2 {
requires javafx.controls;
exports com.mycompany.javafx_test_2;
opens com.mycompany.javafx_test_2 to com.google.gson;
requires com.google.gson;
}
Using GSON version 2.8.9.
Output

How to prevent Gson serialize / deserialize the first character of a field (underscore)?

My class:
class ExampleBean {
private String _firstField;
private String _secondField;
// respective getters and setters
}
I want to appear as follows:
{
"FirstField":"value",
"SecondField":"value"
}
And not like this
{
"_FirstField":"value",
"_SecondField":"value"
}
I initialize the parser as follows:
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat(DateFormat.LONG);
builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
builder.setPrettyPrinting();
set_defaultParser(builder.create());
I could see the API and in the documentation of "FieldNamePolicy" but I am surprised that not give the option to skip "_"
I also know I can use the annotation...
# SerializedName (" custom_naming ")
...but do not want to have to write this for alllllll my fields ...
It's very useful for me to distinguish between local variables and fields of a class. :( Any Idea?
EDIT: There would be many obvious solutions, (inheritance, gson overwriting methods, regular expresions). My question is more focused on whether there is a native solution of gson or a less intrusive fix?
Maybe we could propose as new FieldNamePolicy?
GsonBuilder provides a method setFieldNamingStrategy() that allows you to pass your own FieldNamingStrategy implementation.
Note that this replaces the call to setFieldNamingPolicy() - if you look at the source for GsonBuilder these two methods are mutually exclusive as they set the same internal field (The FieldNamingPolicy enum is a FieldNamingStrategy).
public class App
{
public static void main(String[] args)
{
Gson gson = new GsonBuilder()
.setFieldNamingStrategy(new MyFieldNamingStrategy())
.setPrettyPrinting()
.create();
System.out.println(gson.toJson(new ExampleBean()));
}
}
class ExampleBean
{
private String _firstField = "first field value";
private String _secondField = "second field value";
// respective getters and setters
}
class MyFieldNamingStrategy implements FieldNamingStrategy
{
public String translateName(Field field)
{
String fieldName =
FieldNamingPolicy.UPPER_CAMEL_CASE.translateName(field);
if (fieldName.startsWith("_"))
{
fieldName = fieldName.substring(1);
}
return fieldName;
}
}
Output:
{
"FirstField": "first field value",
"SecondField": "second field value"
}
What you want is
import java.lang.reflect.Field;
import java.text.DateFormat;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonExample {
public static void main(String... args) throws Exception {
final GsonBuilder builder = new GsonBuilder();
builder.setDateFormat(DateFormat.LONG);
builder.setPrettyPrinting();
builder.setFieldNamingStrategy(new FieldNamingStrategy() {
#Override
public String translateName(Field f) {
String fieldName = f.getName();
if(fieldName.startsWith("_") && fieldName.length() > 1) {
fieldName = fieldName.substring(1, 2).toUpperCase() + fieldName.substring(2);
}
return fieldName;
}
});
final Gson gson = builder.create();
System.out.println(gson.toJson(new ExampleBean("example", "bean")));
}
private static class ExampleBean {
private final String _firstField;
private final String _secondField;
private ExampleBean(String _firstField, String _secondField) {
this._firstField = _firstField;
this._secondField = _secondField;
}
}
}
which generates
{"FirstField":"example","SecondField":"bean"}

GSON not handlig initialized static list correctly

If I do this:
public static volatile ArrayList<Process> processes = new ArrayList<Process>(){
{
add(new Process("News Workflow", "This is the workflow for the news segment", "image"));
}
};
and then this:
String jsonResponse = gson.toJson(processes);
jsonResponse is null.
But if I do this:
public static volatile ArrayList<Process> processes = new ArrayList<Process>();
processes.add(new Process("nam", "description", "image"));
String jsonResponse = gson.toJson(processes);
Json response is:
[{"name":"nam","description":"description","image":"image"}]
Why is that?
I do not know what is the problem with Gson, but do you know, that you are creating subclass of ArrayList here?
new ArrayList<Process>(){
{
add(new Process("News Workflow", "This is the workflow for the news segment", "image"));
}
};
You can check that by
System.out.println( processes.getClass().getName() );
it won't print java.util.ArrayList.
I think you wanted to use static initialization as
public static volatile ArrayList<Process> processes = new ArrayList<Process>();
static {
processes.add( new Process( "News Workflow", "This is the workflow for the news segment", "image" ) );
};
It seems that there is problem with anonymous classes, same problem is here
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GSonAnonymTest {
interface Holder {
String get();
}
static Holder h = new Holder() {
String s = "value";
#Override
public String get() {
return s;
}
};
public static void main( final String[] args ) {
final GsonBuilder gb = new GsonBuilder();
final Gson gson = gb.create();
System.out.println( "h:" + gson.toJson( h ) );
System.out.println( h.get() );
}
}
UPD: look at Gson User Guide - Finer Points with Objects, last point "...anonymous classes, and local classes are ignored and not included in serialization or deserialization..."

How to serialize such a custom type to json with google-gson?

First, I have a very simple java bean which can be easily serialized to json:
class Node {
private String text;
// getter and setter
}
Node node = new Node();
node.setText("Hello");
String json = new Gson().toJson(node);
// json is { text: "Hello" }
Then in order to make such beans have some dynamic values, so I create a "WithData" base class:
Class WithData {
private Map<String, Object> map = new HashMap<String, Object>();
public void setData(String key, Object value) { map.put(key, value); }
public Object getData(String key) = { return map.get(key); }
}
class Node extends WithData {
private String text;
// getter and setter
}
Now I can set more data to a node:
Node node = new Node();
node.setText("Hello");
node.setData("to", "The world");
But Gson will ignore the "to", the result is still { text: "Hello" }. I expect it to be: { text: "Hello", to: "The world" }
Is there any way to write a serializer for type WithData, that all classes extend it will not only generate its own properties to json, but also the data in the map?
I tried to implement a custom serializer, but failed, because I don't know how to let Gson serialize the properties first, then the data in map.
What I do now is creating a custom serializer:
public static class NodeSerializer implements JsonSerializer<Node> {
public JsonElement serialize(Node src,
Type typeOfSrc, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty("id", src.id);
obj.addProperty("text", src.text);
obj.addProperty("leaf", src.leaf);
obj.addProperty("level", src.level);
obj.addProperty("parentId", src.parentId);
obj.addProperty("order", src.order);
Set<String> keys = src.getDataKeys();
if (keys != null) {
for (String key : keys) {
obj.add(key, context.serialize(src.getData(key)));
}
}
return obj;
};
}
Then use GsonBuilder to convert it:
Gson gson = new GsonBuilder().
registerTypeAdapter(Node.class, new NodeSerializer()).create();
Tree tree = new Tree();
tree.addNode(node1);
tree.addNode(node2);
gson.toJson(tree);
Then the nodes in the tree will be converted as I expected. The only boring thing is that I need to create a special Gson each time.
Actually, you should expect Node:WithData to serialize as
{
"text": "Hello",
"map": {
"to": "the world"
}
}
(that's with "pretty print" turned on)
I was able to get that serialization when I tried your example. Here is my exact code
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
public class Class1 {
public static void main(String[] args) throws MalformedURLException {
GsonBuilder gb = new GsonBuilder();
Gson g = gb.setPrettyPrinting().create();
Node n = new Node();
n.setText("Hello");
n.setData("to", "the world");
System.out.println(g.toJson(n));
}
private static class WithData {
private Map<String, Object> map = new HashMap<String, Object>();
public void setData(String key, Object value) { map.put(key, value); }
public Object getData(String key) { return map.get(key); }
}
private static class Node extends WithData {
private String text;
public Node() { }
public String getText() {return text;}
public void setText(String text) {this.text = text;}
}
}
I was using the JDK (javac) to compile - that is important because other compilers (those included with some IDEs) may remove the information on which Gson relies as part of their optimization or obfuscation process.
Here are the compilation and execution commands I used:
"C:\Program Files\Java\jdk1.6.0_24\bin\javac.exe" -classpath gson-2.0.jar Class1.java
"C:\Program Files\Java\jdk1.6.0_24\bin\java.exe" -classpath .;gson-2.0.jar Class1
For the purposes of this test, I put the Gson jar file in the same folder as the test class file.
Note that I'm using Gson 2.0; 1.x may behave differently.
Your JDK may be installed in a different location than mine, so if you use those commands, be sure to adjust the path to your JDK as appropriate.

Jackson complex list serialization

I'm experementing with Jackson serialization/deserialization.
For instance, I have such class:
class Base{
String baseId;
}
And I want to serialize List objs;
To do it with jackson, I need to specify a list's elements real type, due to the java type erasure.
This code will work:
List<Base> data = getData();
return new ObjectMapper().writerWithType(TypeFactory.collectionType(List.class, Base.class)).writeValueAsString(data);
Now, I want to serialize more complex class:
class Result{
List<Base> data;
}
How should I tell Jackson to properly serialize this class?
Just
new ObjectMapper().writeValueAsString(myResult);
The type of the list won't be lost due to type erasure in the same way it would be in the first example.
Note that for vanilla serialization of a list or generic list, it's not necessary to specify the list component types, as demonstrated in the example in the original question. All three of the following example serializations represent the List<Bar> with the exact same JSON.
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Baz baz = new Baz("BAZ", 42);
Zab zab = new Zab("ZAB", true);
List<Bar> bars = new ArrayList<Bar>();
bars.add(baz);
bars.add(zab);
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
String json1 = mapper.writeValueAsString(bars);
System.out.println(json1);
// output:
// [{"name":"BAZ","size":42},{"name":"ZAB","hungry":true}]
Foo foo = new Foo(bars);
String json2 = mapper.writeValueAsString(foo);
System.out.println(json2);
// output:
// {"bars":[{"name":"BAZ","size":42},{"name":"ZAB","hungry":true}]}
mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
ObjectWriter typedWriter = mapper.writerWithType(mapper.getTypeFactory().constructCollectionType(List.class, Bar.class));
String json3 = typedWriter.writeValueAsString(bars);
System.out.println(json3);
// output:
// [{"name":"BAZ","size":42},{"name":"ZAB","hungry":true}]
}
}
class Foo
{
List<Bar> bars;
Foo(List<Bar> b) {bars = b;}
}
abstract class Bar
{
String name;
Bar(String n) {name = n;}
}
class Baz extends Bar
{
int size;
Baz(String n, int s) {super(n); size = s;}
}
class Zab extends Bar
{
boolean hungry;
Zab(String n, boolean h) {super(n); hungry = h;}
}
A typed writer is useful when serializing with additional type information. Note how the json1 and json3 outputs below differ.
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectMapper.DefaultTyping;
import org.codehaus.jackson.map.ObjectWriter;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Baz baz = new Baz("BAZ", 42);
Zab zab = new Zab("ZAB", true);
List<Bar> bars = new ArrayList<Bar>();
bars.add(baz);
bars.add(zab);
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
mapper.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE, "type");
String json1 = mapper.writeValueAsString(bars);
System.out.println(json1);
// output:
// [
// {"type":"com.stackoverflow.q8416904.Baz","name":"BAZ","size":42},
// {"type":"com.stackoverflow.q8416904.Zab","name":"ZAB","hungry":true}
// ]
Foo foo = new Foo(bars);
String json2 = mapper.writeValueAsString(foo);
System.out.println(json2);
// output:
// {
// "bars":
// [
// "java.util.ArrayList",
// [
// {"type":"com.stackoverflow.q8416904.Baz","name":"BAZ","size":42},
// {"type":"com.stackoverflow.q8416904.Zab","name":"ZAB","hungry":true}
// ]
// ]
// }
mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
mapper.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE, "type");
ObjectWriter typedWriter = mapper.writerWithType(mapper.getTypeFactory().constructCollectionType(List.class, Bar.class));
String json3 = typedWriter.writeValueAsString(bars);
System.out.println(json3);
// output:
// [
// "java.util.ArrayList",
// [
// {"type":"com.stackoverflow.q8416904.Baz","name":"BAZ","size":42},
// {"type":"com.stackoverflow.q8416904.Zab","name":"ZAB","hungry":true}
// ]
// ]
}
}