Jackson complex list serialization - json

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}
// ]
// ]
}
}

Related

Trying to fetch data from a JSON file and pass the data into fragment, but get an error

I am new to Kotlin and Android Studio and I am tasked with creating a tab layout with 3 tabs and with view pager and fragments. Each fragment will have a list of songs from different genres: Rock, Pop, and Classic. I am given APIs and I created json files from these APIs. I created the Xmls and setup the layouts, adapters, data classes, and everything. Now I'm trying to get the data from the json file into the fragment using recycler view and card view. Here is the error I am getting:
Type mismatch: inferred type is MainActivity but List was expected and Classifier 'MyAdapter' does not have a companion object, and thus must be initialized here. Here is my code:
//Main Activity
package com.example.itunes_mysia
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.nio.charset.Charset
var artist_name: ArrayList<String> = ArrayList()
var track_name: ArrayList<String> = ArrayList()
var track_price: ArrayList<String> = ArrayList()
class MainActivity : AppCompatActivity() {
private lateinit var itunesToolbar: androidx.appcompat.widget.Toolbar
private lateinit var itunesTabs: TabLayout
private lateinit var itunesTitleText: TextView
private lateinit var itunesViewPager: ViewPager
private lateinit var itunesPagerAdapters: PagerAdapters
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "KotlinApp"
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
val linearLayoutManager = LinearLayoutManager(applicationContext)
recyclerView.layoutManager = linearLayoutManager
try {
val obj = JSONObject(loadJSONFromAsset())
val rockArray = obj.getJSONArray("Rock")
for (i in 0 until rockArray.length()) {
val userDetail = rockArray.getJSONObject(i)
artist_name.add(userDetail.getString("Artist"))
track_name.add(userDetail.getString("Track Name"))
track_price.add(userDetail.getString("Track Price"))
}
}
catch (e: JSONException) {
e.printStackTrace()
}
val myAdapter = MyAdapter(this#MainActivity)
recyclerView.adapter = MyAdapter
// Set find ID
itunesToolbar = findViewById(R.id.itunesToolbar)
itunesTitleText = findViewById(R.id.itunesTitleText)
itunesTabs = findViewById(R.id.itunesTabs)
itunesViewPager = findViewById(R.id.itunesViewPager)
itunesPagerAdapters = PagerAdapters(supportFragmentManager)
// Set Toolbar
itunesToolbar.setTitle("")
itunesTitleText.setText(getString(R.string.itunes))
setSupportActionBar(findViewById(R.id.itunesToolbar))
// Set Fragment List
itunesPagerAdapters.addfragment(RockFragment(), "Rock")
itunesPagerAdapters.addfragment(ClassicFFragment(), "Classic")
itunesPagerAdapters.addfragment(PopFragment(), "Pop")
// Set View Pager Adapter
itunesViewPager.adapter = itunesPagerAdapters
// Set Tab Layout with View Pager Adapter
itunesTabs.setupWithViewPager(itunesViewPager)
// Set Icons
itunesTabs.getTabAt(0)!!.setIcon(R.mipmap.music1)
itunesTabs.getTabAt(1)!!.setIcon(R.mipmap.music2)
itunesTabs.getTabAt(2)!!.setIcon(R.mipmap.music3)
}
class ViewPagerOnTabSelectedListener(private val viewPager: ViewPager) :
OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
viewPager.currentItem = tab.position
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
// No-op
}
override fun onTabReselected(tab: TabLayout.Tab?) {
// No-op
}
}
private fun loadJSONFromAsset(): String {
val json: String?
try {
val inputStream = assets.open("rock.json")
val size = inputStream.available()
val buffer = ByteArray(size)
val charset: Charset = Charsets.UTF_8
inputStream.read(buffer)
inputStream.close()
json = String(buffer, charset)
}
catch (ex: IOException) {
ex.printStackTrace()
return ""
}
return json
}
}
//MyAdapter.kt (for recycler view)
package com.example.itunes_mysia
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class MyAdapter(private val Rock: List<Rock>) :
RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
MyViewHolder {
val itemView= LayoutInflater.from(parent.context).inflate(R.layout.row, parent,
false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = Rock[position]
holder.artistName.text = currentItem.artistName
holder.trackName.text = currentItem.trackName
holder.trackPrice.text = currentItem.trackPrice
}
override fun getItemCount(): Int {
return Rock.size
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val artistName: TextView = itemView.findViewById(R.id.art_name)
val trackName: TextView = itemView.findViewById(R.id.trackName)
val trackPrice: TextView = itemView.findViewById(R.id.trackPrice)
}
}

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 convert a complex object, having a HashMap parameter, into JSON and back? [duplicate]

I get the error:
Exception in thread "main" com.google.gson.JsonParseException:
Expecting object found: "com.shagie.app.SimpleMap$Data#24a37368"
when trying to deseralize a Map that uses non-trivial keys:
package com.shagie.app;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.HashMap;
public class SimpleMap {
public static void main(String[] args) {
Wrapper w = new Wrapper();
w.m.put(new Data("f", 1), new Data("foo", 3));
w.m.put(new Data("b", 2), new Data("bar", 4));
GsonBuilder gb = new GsonBuilder();
gb.setPrettyPrinting();
Gson g = gb.create();
String json = g.toJson(w);
System.out.println(json);
w = g.fromJson(json, Wrapper.class);
System.out.println(w.m.isEmpty());
}
static public class Wrapper {
HashMap<Data, Data> m = new HashMap<Data, Data>();
}
static public class Data {
String s;
Integer i;
public Data(String arg, Integer val) { s = arg; i = val; }
}
}
This serializes to the json:
{
"m": {
"com.shagie.app.SimpleMap$Data#24a37368": {
"s": "foo",
"i": 3
},
"com.shagie.app.SimpleMap$Data#66edc3a2": {
"s": "bar",
"i": 4
}
}
}
One can see the key attempting to be serialized, but certainly not in a way that can be deserialized.
How does one serialize this object so that it can be deserialized?
I found the following while trying to solve this puzzle: Issue 210: Cannot serialize or deserialize Maps with complex keys.
For any internet travelers from the future (like myself)... you can enable this functionality in GSON 2.* with the enableComplexMapKeySerialization() method on GsonBuilder.
Here's the javadoc for that method.
When enabled, the map will be serialized (and correctly deserialized) as an array of [key, value] arrays:
{"m":[[{"s":"f", "i",1}, {"s":"foo", "i":3}], [{"s":"b", "i",2}, {"s":"bar", "i":4}]]}
The problem is that toString() is getting called on the keys to the map, rather than them being serialized themselves.
To fix this a custom serializer and deserializer needs to be set up, and the deserializer needs to be aware of the format that the object uses to display itself as a string (the toString() method must return a string that can be used to reconstruct the entire object).
For the above example:
package com.shagie.app;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.util.HashMap;
public class SimpleMapFixed {
public static void main(String[] args) {
Wrapper w = new Wrapper();
w.m.put(new Data("f", 1), new Data("foo", 3));
w.m.put(new Data("b", 2), new Data("bar", 4));
GsonBuilder gb = new GsonBuilder();
gb.setPrettyPrinting();
gb.registerTypeAdapter(Data.class, new DataSerializer());
Gson g = gb.create();
String json = g.toJson(w);
System.out.println(json);
w = g.fromJson(json, Wrapper.class);
System.out.println(w.m.isEmpty());
}
static public class Wrapper {
HashMap<Data, Data> m = new HashMap<Data, Data>();
}
static public class DataSerializer implements JsonSerializer<Data>,
JsonDeserializer<Data> {
#Override
public Data deserialize(JsonElement je, Type t, JsonDeserializationContext ctx)
throws JsonParseException {
Data rv;
JsonObject jo;
System.out.println("deserialize called with: " + je.toString());
if (je.isJsonObject()) {
jo = je.getAsJsonObject();
rv = new Data(jo.get("s").getAsString(), jo.get("i").getAsInt());
} else {
String js = je.getAsString();
String[] s = js.split(":", 2); // split into two (and only two)
rv = new Data(s[1], Integer.valueOf(s[0]));
}
System.out.println("deserialize returns: " + rv.s + " " + rv.i);
return rv;
}
#Override
public JsonElement serialize(Data data, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject jo = new JsonObject();
jo.addProperty("s", data.s);
jo.addProperty("i", data.i);
System.out.println("serialize called: " + jo.toString());
return jo;
}
}
static public class Data {
String s;
Integer i;
public Data(String arg, Integer val) { s = arg; i = val; }
#Override
public String toString() {
String rv = i.toString() + ':' + s;
System.out.println("toString called: " + rv);
return rv;
}
}
}
Running this code produces:
serialize called: {"s":"foo","i":3}
toString called: 1:f
serialize called: {"s":"bar","i":4}
toString called: 2:b
{
"m": {
"1:f": {
"s": "foo",
"i": 3
},
"2:b": {
"s": "bar",
"i": 4
}
}
}
deserialize called with: "1:f"
deserialize returns: f 1
deserialize called with: {"s":"foo","i":3}
deserialize returns: foo 3
deserialize called with: "2:b"
deserialize returns: b 2
deserialize called with: {"s":"bar","i":4}
deserialize returns: bar 4
Note the invocations of toString() as part of the serialization. In this code, the logic for the deserializion from the String form is in the DataSerializer, though it may make sense to move it into the Data class as another constructor instead - it doesn't affect the final outcome.
Further note that Data was a rather simple object itself with no deeper structures. Trying to serialize that as the key would require additional work.
Its Up to you how you are maintaining the HahMap Keys, You can deserialized it with simple and easiest way.
final Type typeOf = new TypeToken <Map<String, Map<String, Data>>>(){}.getType();
final Map<String, Map<String, Data>> newMap = gson.fromJson(json, typeOf);
final Map<String, Data> map = newMap.get("m");
final Iterator<Entry<String, Data>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Data> pair = (Map.Entry<String,Data>) it.next();
String key = pair.getKey();
System.out.println("key "+ key + " Values[ i= " + data.getI() + ", s= " +data.getS()+" ]");
}
Result:
key = snippet.Snippet$Data#61506150 Values [ i= 3, s= foo ]
key = snippet.Snippet$Data#63ff63ff Values [ i= 4, s= bar ]

Difference between '{' and '[' when formatting JSON object

Is there any difference between '{' and '[' when formatting a JSON object?
Yep one {...} is used to define a single object, while the other [...] is used to define a sequence of either objects, values or lists ...
objects are defined as such {key:object or list or value , ...}
list ares nothing more than a sequence of either objects or lists or values, [objects or list or values, ... ]...
[{'value':1}, {'values':[1,2,3,3, {'a':'a', 'b':'b'}]}, 2, 3, 4]
'{ } ' used for Object and '[]' is used for Array in json
Like
var sampleObj = {
a:1,
b:'ab'
};
var sampleArr = [1,'ab',4];
They don't have the same meaning at all. {} denote containers, [] denote arrays.
package ravi.kumar;
import java.util.ArrayList;
import java.lang.Object;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class SetListClass {
public static void main(String[] args) {
SetListClass SetListClass = new SetListClass();
List<String> list = new ArrayList<String>();
list.add("country");
list.add("state");
list.add("distract");
list.add("country");
System.out.println(list);
System.out.println("----------------------------------------------");
SetListClass.getset();
System.out.println("----------------------------------------------");
SetListClass.getHashMap();
}
public void getset()
{
Set<String> set = new HashSet<String>();
set.add("country");
set.add("state");
set.add("distract");
set.add("country");
System.out.println(set);
System.out.println(set.remove("country"));
System.out.println("---------------------------------------------");
System.out.println(set);
}
public void getHashMap() {
HashMap<String, Object> hashmap = new HashMap<String, Object>();
hashmap.put("country", "india");
hashmap.put("state", "bihar");
hashmap.put("district", "buxar");
System.out.println(hashmap);
}
}
output
-------
[country, state, distract, country] ------array
----------------------------------------------
[state, distract, country] ----array
true
---------------------------------------------
[state, distract]
----------------------------------------------
{state=bihar, district=buxar, country=india} ---Object

Global property filter in Jackson

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