Apache Flink : Window Function on AllWindowed Stream - Combining Kafka Topics - json

I am trying to combine two kafka topics using the a single kafka consumer on a list of topics, further convert the json string in the stream to POJO. Then, join them via keyBy ( On event time field ) and to merge them as a single fat json, I was planning to use a window stream and apply a window function on the window stream. The assumption is that Topic-A & Topic-B can be joined on Event Time and only one pair ( Topic A ( JSON ) , Topic B (JSON ) will be present with the same eventTime. Hence was planning to use a coutWindow(2) post keyBy on eventTime.
I have couple of questions for the same;
Is the approach fine for merging topics and creating a single JSON?
The window function on All Window stream doesnt seem to work fine; Any pointers will be greatly appreciated.
Code Snippet :
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
logger.info("Flink Stream Window Charger has started");
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "127.0.0.1:1030");
properties.setProperty("zookeeper.connect", "127.0.0.1:2181/service-kafka");
properties.setProperty("group.id", "group-0011");
properties.setProperty("auto.offset.reset", "smallest");
List < String > names = new ArrayList < > ();
names.add("Topic-A");
names.add("Topic-B");
DataStream < String > stream = env.addSource(new FlinkKafkaConsumer08 < > (names, new SimpleStringSchema(), properties));
DataStream < TopicPojo > pojo = stream.map(new Deserializer()).keyBy((eventTime) -> TopicPojo.getEventTime());
List < String > where = new ArrayList < String > ();
AllWindowedStream < String, GlobalWindow > data_window = pojo.flatMap(new Tokenizer()).countWindowAll(2);
DataStream < String > data_charging = data_window.apply(new MyWindowFunction());
data_charging.addSink(new SinkFunction < String > () {
public void invoke(String value) throws Exception {
// Yet to be implemented - Merge two POJO into one
}
});
try
{
env.execute();
} catch (Exception e)
{
return;
}
}
}
class Tokenizer implements FlatMapFunction < TopicPojo, String > {
private static final long serialVersionUID = 1 L;
#Override
public void flatMap(TopicPojo value, Collector < String > out) throws Exception {
ObjectMapper mapper = new ObjectMapper();
out.collect(mapper.writeValueAsString(value));
}
}
class MyWindowFunction implements WindowFunction < TopicPojo, String, String, GlobalWindow > {
#Override
public void apply(String key, GlobalWindow window, Iterable < TopicPojo > arg2, Collector < String > out)
throws Exception {
int count = 0;
for (TopicPojo in : arg2) {
count++;
}
// Test Result - TO be modified
out.collect("Window: " + window + "count: " + count);
}
}
class Deserializer implements MapFunction < String, TopicPojo > {
private static final long serialVersionUID = 1 L;
#Override
public TopicPojo map(String value) throws IOException {
// TODO Auto-generated method stub
ObjectMapper mapper = new ObjectMapper();
TopicPojo obj = null;
try {
System.out.println(value);
obj = mapper.readValue(value, TopicPojo.class);
} catch (JsonParseException e) {
// TODO Auto-generated catch block
throw new IOException("Failed to deserialize JSON object.");
} catch (JsonMappingException e) {
// TODO Auto-generated catch block
throw new IOException("Failed to deserialize JSON object.");
} catch (IOException e) {
// TODO Auto-generated catch block
throw new IOException("Failed to deserialize JSON object.");
}
return obj;
}
}
I am getting -
The method apply(AllWindowFunction) in the type AllWindowedStream is not applicable for the arguments (MyWindowFunction) error.

An AllWindowedStream is a non-keyed stream, and so the apply method for AllWindowedStreams doesn't have a key parameter. Since you are windowing a keyed stream, your data_window should be a KeyedStream.

Related

How to correctly handle data management with SharedPreferences?

Right now, I am in the process of "optimizing" my app. I am still a beginner, so what I am doing is basically moving methods from my MainActivity.class to their separate class. I believe it's called Encapsulation (Please correct me if I'm wrong).
My application needs to :
Get a YouTube Playlist Link from the YouTube App (with an Intent, android.intent.action.SEND).
Use the link to fetch data from the Google Servers with the YouTubeApi and Volley.
Read the data received and add it to an arrayList<String>.
What my YouTubeUsage.java class is supposed to do, is fetch data with the YouTubeApi and Volley then store the data using SharedPreferences. Once the data is saved, the data is being read in my ConvertActivity.class (It's an activity specifically created for android.intent.action.SEND) with my method getVideoIds() before setting an adapter for my listView in my createRecyclerView() method.
YouTubeUsage.java
public class YoutubeUsage {
private Boolean results = false;
private String mResponse;
private ArrayList<String> videoIds = new ArrayList<>();
String Url;
public String getUrl(String signal) {
String playlistId = signal.substring(signal.indexOf("=") + 1);
this.Url = "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" +
playlistId + "&maxResults=25&key=" + "API_KEY";
return this.Url;
}
public void fetch(String Url, final Context context){
RequestQueue queue = Volley.newRequestQueue(context);
StringRequest request = new StringRequest(Request.Method.GET, Url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
sharedPreferences(response, context);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("VolleyError", Objects.requireNonNull(error.getMessage()));
}
});
queue.add(request);
}
private void sharedPreferences(String response, Context context){
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = m.edit();
if (m.contains("serverResponse")){
if (!m.getString("serverResponse", "").equals(response)){
editor.remove("serverResponse");
editor.apply();
updateSharedPreferences(response, context);
}
} else{
updateSharedPreferences(response, context);
}
}
private void updateSharedPreferences(String mResponse, Context mContext){
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = m.edit();
editor.putString("serverResponse", mResponse);
editor.apply();
}
}
ConvertActivity.java
public class ConvertActivity extends AppCompatActivity {
YoutubeUsage youtubeUsage = new YoutubeUsage();
ArrayList<String> videoIDs = new ArrayList<>();
String Url = "";
ListView listView;
MyCustomAdapter myCustomAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_convert);
listView = findViewById(R.id.listview_convert);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
}
//I would like to avoid the try/catch below
try {
videoIDs = getVideoIDs(Url, this);
createRecyclerView(videoIDs);
Log.i("ResponseVideoIDs", String.valueOf(videoIDs.size()));
} catch (JSONException e) {
e.printStackTrace();
}
}
private ArrayList<String> getVideoIDs(String Url, Context context) throws JSONException {
ArrayList<String> rawVideoIDs = new ArrayList<>();
youtubeUsage.fetch(Url, context);
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
String serverResponse = m.getString("serverResponse", "");
JSONObject jsonObject = new JSONObject(serverResponse);
JSONArray jsonArray = jsonObject.getJSONArray("items");
for (int i = 0; i<jsonArray.length(); i++){
JSONObject jsonObject1 = jsonArray.getJSONObject(i);
JSONObject jsonVideoId = jsonObject1.getJSONObject("contentDetails");
rawVideoIDs.add(jsonVideoId.getString("videoId"));
}
return rawVideoIDs;
}
private void createRecyclerView(ArrayList<String> videoIDs){
myCustomAdapter = new MyCustomAdapter(this, videoIDs);
listView.setAdapter(myCustomAdapter);
myCustomAdapter.notifyDataSetChanged();
}
}
Everything works fine, however, my sharedPreferences never gets updated. Which means, if I share a YouTube playlist from the YouTube App to my app with 3 items in it, it will work fine. The Listview will show 3 items with their corresponding IDs as it should. But, if I share a YouTube playlist again, my app will still hold on to the data of the previous playlist I shared (even if I close it), showing the item number and the IDs of the previous link. If i continue to share the same playlist over and over, it will eventually show the correct number of items and the correct IDs.
I could totally put all my methods from the YouTubeUsage.java in my ConvertActivity.class preventing me from using SharedPreferences to transfer data between the two java classes. However, JSON throws an exception. That means I have to encapsulate my code with try/catch. I would like to avoid those since I need to do a lot of operations on the data just received by Volley (check a class size, look for certains strings). I find that doing this in these try/catch don't work like I want. (i.e. outside the try/catch, the values remains the same even if I updated them in the try/catch).
I want to know two things.
How can I correct this problem?
Is this the most efficient way to do this (optimization)? (I though of maybe
converting the VolleyResponse to a string with Gson then store the String file, but I don't know if that's the best way to do it since it's supposed to be
provisional data. It feels like just more of the same).
Thank You!
There is an issue with making assumptions about order of events. Volley will handle requests asynchronously, so it is advisable to implement the observer pattern here.
Create a new Java file that just contains:
interface MyNetworkResponse {
void goodResponse(String responseString);
}
Then make sure ConvertActivity implements MyNetworkResponse and create method:
void goodResponse(String responseString) {
// handle a positive response here, i.e. extract the JSON and send to your RecyclerView.
}
within your Activity.
In your YoutubeUsage constructor, pass in the Activity context (YoutubeUsage) and then store this in a YoutubeUsage instance variable called ctx.
In onCreate, create an instance of YoutubeUsage and pass in this.
In onResponse just call ctx.goodResponse(response).
Amend the following block to:
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
youtubeUsage.fetch(Url);
}
Delete the try/catch from onCreate.
And no need to use SharedPreferences at all.
UPDATE
Try this code:
MyNetworkResponse.java
interface MyNetworkResponse {
void goodResponse(String responseString);
void badResponse(VolleyError error);
}
YoutubeUsage.java
class YoutubeUsage {
private RequestQueue queue;
private MyNetworkResponse callback;
YoutubeUsage(Object caller) {
this.callback = (MyNetworkResponse) caller;
queue = Volley.newRequestQueue((Context) caller);
}
static String getUrl(String signal) {
String playlistId = signal.substring(signal.indexOf("=") + 1);
return "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" + playlistId + "&maxResults=25&key=" + "API_KEY";
}
void fetch(String url){
StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
callback.goodResponse(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
callback.badResponse(error);
}
});
queue.add(request);
}
}
ConvertActivity.java
public class ConvertActivity extends AppCompatActivity implements MyNetworkResponse {
YoutubeUsage youtubeUsage;
ArrayList<String> videoIDs = new ArrayList<>();
ListView listView;
MyCustomAdapter myCustomAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_convert);
listView = findViewById(R.id.listview_convert);
youtubeUsage = new YoutubeUsage(this);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
String url = YoutubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
youtubeUsage.fetch(url);
}
}
private ArrayList<String> getVideoIDs(String serverResponse) throws JSONException {
ArrayList<String> rawVideoIDs = new ArrayList<>();
JSONObject jsonObject = new JSONObject(serverResponse);
JSONArray jsonArray = jsonObject.getJSONArray("items");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject1 = jsonArray.getJSONObject(i);
JSONObject jsonVideoId = jsonObject1.getJSONObject("contentDetails");
rawVideoIDs.add(jsonVideoId.getString("videoId"));
}
return rawVideoIDs;
}
private void createRecyclerView(ArrayList<String> videoIDs) {
myCustomAdapter = new MyCustomAdapter(this, videoIDs);
listView.setAdapter(myCustomAdapter);
myCustomAdapter.notifyDataSetChanged();
}
#Override
public void goodResponse(String responseString) {
Log.d("Convert:goodResp", "[" + responseString + "]");
try {
ArrayList<String> rawVideoIDs = getVideoIDs(responseString);
createRecyclerView(rawVideoIDs);
} catch (JSONException e) {
// handle JSONException, e.g. malformed response from server.
}
}
#Override
public void badResponse(VolleyError error) {
// handle unwanted server response.
}
}

Increasing maxjsonlength on MVC post from Javascript

I have a controller action Export which accepts a List of models like below. This is sending back and manipulated dataset back from the view where the user could interact with it. So we have been able to send the data down with much more information.
[HttpPost]
public JsonResult Export(List<MappingExportModel> sources){}
This works fine in all cases but there is one where we have a bigger than normal dataset. This is causing an issue with the export. So far I have tried just passing the values as an object or string but I am unable to convert them into any usable instance after the data is into the controller.
Is it possible to preemptively increase this maxjsonlength value somewhere. The value from the web.config is being ignored from what I have come across so far.
The error I receive is
"Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property"
I need to be able to accept this directly from the ajax request into the controller action. Spinning up a version of JsonResult and then setting the max value will not work because the error is thrown the the data is trying to be deserialized into the object var presented above. We get the value in the original GET request and do set the value before the view is loaded. Now we are taking the data from this view and sending it back plus all the manipulations the users have created.
User posts data to server, the controller action is hit with the data. The error is encountered and spit back out to the browser which handles the error.
You can use custom json length. add the following file in your project and edit your global.asax.cs
Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
///// **********
JsonValueProviderFactory jsonValueProviderFactory = null;
foreach (var factory in ValueProviderFactories.Factories)
{
if (factory is JsonValueProviderFactory)
{
jsonValueProviderFactory = factory as JsonValueProviderFactory;
}
}
//remove the default JsonVAlueProviderFactory
if (jsonValueProviderFactory != null) ValueProviderFactories.Factories.Remove(jsonValueProviderFactory);
//add the custom one
ValueProviderFactories.Factories.Add(new CustomJsonValueProviderFactory());
/////*************
}
}
///******** for json length
public sealed class CustomJsonValueProviderFactory : ValueProviderFactory
{
private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
{
IDictionary<string, object> d = value as IDictionary<string, object>;
if (d != null)
{
foreach (KeyValuePair<string, object> entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
}
IList l = value as IList;
if (l != null)
{
for (int i = 0; i < l.Count; i++)
{
AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
}
return;
}
// primitive
backingStore[prefix] = value;
}
private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
// not JSON request
return null;
}
StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
string bodyText = reader.ReadToEnd();
if (String.IsNullOrEmpty(bodyText))
{
// no JSON data
return null;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.MaxJsonLength = int.MaxValue; //increase MaxJsonLength. This could be read in from the web.config if you prefer
object jsonData = serializer.DeserializeObject(bodyText);
return jsonData;
}
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
object jsonData = GetDeserializedObject(controllerContext);
if (jsonData == null)
{
return null;
}
Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
AddToBackingStore(backingStore, String.Empty, jsonData);
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName)
{
return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
}
}
JsonValueProviderFactory.cs
public sealed class JsonValueProviderFactory : ValueProviderFactory
{
private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
{
IDictionary<string, object> d = value as IDictionary<string, object>;
if (d != null)
{
foreach (KeyValuePair<string, object> entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
}
IList l = value as IList;
if (l != null)
{
for (int i = 0; i < l.Count; i++)
{
AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
}
return;
}
// primitive
backingStore[prefix] = value;
}
private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
// not JSON request
return null;
}
StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
string bodyText = reader.ReadToEnd();
if (String.IsNullOrEmpty(bodyText))
{
// no JSON data
return null;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.MaxJsonLength = int.MaxValue; //increase MaxJsonLength. This could be read in from the web.config if you prefer
object jsonData = serializer.DeserializeObject(bodyText);
return jsonData;
}
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
object jsonData = GetDeserializedObject(controllerContext);
if (jsonData == null)
{
return null;
}
Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
AddToBackingStore(backingStore, String.Empty, jsonData);
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName)
{
return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
}
}
by this you can pass lengthy json through ajax to controller and if you want to retrieve a lengthy string back to ajax result from controller then add below code in your controller also
//add this for getting large json string
protected override JsonResult Json(object data, string contentType, System.Text.Encoding contentEncoding, JsonRequestBehavior behavior)
{
return new JsonResult()
{
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding,
JsonRequestBehavior = behavior,
MaxJsonLength = Int32.MaxValue
};
}

MySQL ResultSet into Gson array

I want to put a MySQL result set into a JsonArray using Gsons library. How can I best achieve this. I've read this:
resultset to json using gson
But it uses for some reason, the simple-Json library in addition. i dont want that if possible. Is there any way to achieve this easily with the gson library?
Thank you very much!
PlayerList.java:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package de.freakyonline.ucone;
import de.freakyonline.ucone.Player;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TextArea;
/**
*
* #author uwe
*/
public class PlayerList {
ObservableList<Player> playerList;
ObjectInputStream in;
ObjectOutputStream out;
Socket sock;
private Object obj = null;
private Object obj2 = null;
TextArea consoleOneTextArea;
public PlayerList(ObjectInputStream in, ObjectOutputStream out, Socket sock, TextArea consoleOneTextArea) {
this.in = in;
this.out = out;
this.sock = sock;
this.consoleOneTextArea = consoleOneTextArea;
getPlayersFromServer();
}
private void getPlayersFromServer() {
/* try {
out.writeObject("getplayers");
obj=in.readObject();
if(obj == null) {
System.out.println("ERROR! void getPlayersFromServer in PlayerList.java");
Platform.exit();
}
String command = obj.toString().toLowerCase();
String currentFromServer;
if(command.equalsIgnoreCase("getplayers")) {
while((obj2=in.readObject()) != null) {
currentFromServer = obj2.toString().toLowerCase();
for(String cell : currentFromServer.split(" ")) {
System.out.println(cell.toString());
}
if (currentFromServer.equalsIgnoreCase("done")) {
consoleOneTextArea.appendText("This is finished. Have fun!\n");
break;
}
consoleOneTextArea.appendText(currentFromServer + "\n");
}
} { System.out.println("ERROR"); }
} catch (Exception ex) { ex.printStackTrace(); }
*/
this.playerList = FXCollections.observableArrayList(
new Player("freakyy85","Owner","1810",31,"m", "missing..."),
new Player("Ky3ak","Owner","1920",34,"m", "missing...")
);
}
}
(ive commented out some parts, as they are not relevant anymore)
Player.java:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package de.freakyonline.ucone;
import com.google.gson.stream.JsonReader;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import javafx.scene.control.TextArea;
/**
*
* #author uwe
*/
public class Remote implements Runnable {
private Object obj = null;
private Object obj2 = null;
private ObjectInputStream in;
private ObjectOutputStream out;
private Socket sock;
private TextArea consoleOneTextArea;
public Remote (ObjectInputStream in, ObjectOutputStream out, Socket sock) {
this.in = in;
this.out = out;
this.sock = sock;
}
public ObjectInputStream getIn() {
return in;
}
public ObjectOutputStream getOut() {
return out;
}
public Socket getSock() {
return sock;
}
public void setConsoleOneTextArea(TextArea consoleOneTextArea) {
this.consoleOneTextArea = consoleOneTextArea;
}
public void run() {
try {
while((obj=in.readObject()) != null && sock.isConnected()) {
String command = obj.toString().toLowerCase();
String currentFromServer;
switch(command) {
case "getplayers":
/* while((obj2=in.readObject()) != null) {
currentFromServer = obj2.toString().toLowerCase();
if (currentFromServer.equalsIgnoreCase("done")) {
consoleOneTextArea.appendText("This is finished. Have fun!\n");
break;
}
consoleOneTextArea.appendText(currentFromServer + "\n");
*/ }
JsonReader jsonReader = new JsonReader(new InputStreamReader(in, "UTF-8"));
jsonReader.close();
break;
}
} catch (Exception ex) { ex.printStackTrace(); }
}
}
Is there any way to achieve this easily with the gson library?
Not really. Gson and JDBC are too/two unrelated things so you have to implement a custom remapping function to "decode" JDBC result set rows/fields and "encode" them back to JSON array/object respectively. Accumulating a JsonArray instance may be expensive from the memory consumption point of view, or even crash the application with OutOfMemoryError for huge result sets. Nonetheless they are good if the result sets are known to be small or LIMITed.
Accumulating JsonArray
static JsonArray resultSetToJsonArray(final ResultSet resultSet)
throws SQLException {
final ResultSetMetaData metaData = resultSet.getMetaData();
// JsonArray is a Gson built-in class to hold JSON arrays
final JsonArray jsonArray = new JsonArray();
while ( resultSet.next() ) {
jsonArray.add(resultSetRowToJsonObject(resultSet, metaData));
}
return jsonArray;
}
private static JsonElement resultSetRowToJsonObject(final ResultSet resultSet, final ResultSetMetaData metaData)
throws SQLException {
final int columnCount = metaData.getColumnCount();
// Every result set row is a JsonObject equivalent
final JsonObject jsonObject = new JsonObject();
// JDBC uses 1-based loops
for ( int i = 1; i <= columnCount; i++ ) {
jsonObject.add(metaData.getColumnName(i), fieldToJsonElement(resultSet, metaData, i));
}
return jsonObject;
}
private static JsonElement fieldToJsonElement(final ResultSet resultSet, final ResultSetMetaData metaData, final int column)
throws SQLException {
final int columnType = metaData.getColumnType(column);
final Optional<JsonElement> jsonElement;
// Process each SQL type mapping a value to a JSON tree equivalent
switch ( columnType ) {
case Types.BIT:
case Types.TINYINT:
case Types.SMALLINT:
throw new UnsupportedOperationException("TODO: " + JDBCType.valueOf(columnType));
case Types.INTEGER:
// resultSet.getInt() returns 0 in case of null, so it must be extracted with getObject and cast, then converted to a JsonPrimitive
jsonElement = Optional.ofNullable((Integer) resultSet.getObject(column)).map(JsonPrimitive::new);
break;
case Types.BIGINT:
case Types.FLOAT:
case Types.REAL:
case Types.DOUBLE:
case Types.NUMERIC:
case Types.DECIMAL:
case Types.CHAR:
throw new UnsupportedOperationException("TODO: " + JDBCType.valueOf(columnType));
case Types.VARCHAR:
jsonElement = Optional.ofNullable(resultSet.getString(column)).map(JsonPrimitive::new);
break;
case Types.LONGVARCHAR:
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.NULL:
case Types.OTHER:
case Types.JAVA_OBJECT:
case Types.DISTINCT:
case Types.STRUCT:
case Types.ARRAY:
case Types.BLOB:
case Types.CLOB:
case Types.REF:
case Types.DATALINK:
case Types.BOOLEAN:
case Types.ROWID:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.LONGNVARCHAR:
case Types.NCLOB:
case Types.SQLXML:
case Types.REF_CURSOR:
case Types.TIME_WITH_TIMEZONE:
case Types.TIMESTAMP_WITH_TIMEZONE:
throw new UnsupportedOperationException("TODO: " + JDBCType.valueOf(columnType));
default:
throw new UnsupportedOperationException("Unknown type: " + columnType);
}
// If the optional value is missing, assume it's a null
return jsonElement.orElse(JsonNull.INSTANCE);
}
final JsonArray jsonArray = resultSetToJsonArray(resultSet);
System.out.println(jsonArray);
Don't forget to close the resultSet, of course.
JSON streaming
If the JsonArray is supposed to be written elsewhere, JsonWriter can be a better solution being able to process huge result sets reading row by row and writing JSON element by JSON element.
#SuppressWarnings("resource")
static void resultSetToJsonArrayStream(final ResultSet resultSet, final JsonWriter jsonWriter)
throws SQLException, IOException {
// Write the [ token
jsonWriter.beginArray();
final ResultSetMetaData metaData = resultSet.getMetaData();
while ( resultSet.next() ) {
// Write row by row
writeRow(resultSet, jsonWriter, metaData);
}
// Finish the array with the ] token
jsonWriter.endArray();
}
#SuppressWarnings("resource")
private static void writeRow(final ResultSet resultSet, final JsonWriter jsonWriter, final ResultSetMetaData metaData)
throws SQLException, IOException {
final int columnCount = metaData.getColumnCount();
// Similarly to the outer array: the { token starts a new object representing a row
jsonWriter.beginObject();
for ( int i = 1; i <= columnCount; i++ ) {
// Write the column name and try to resolve a JSON literal to be written
jsonWriter.name(metaData.getColumnName(i));
writeField(resultSet, jsonWriter, metaData, i);
}
// Terminate the object with }
jsonWriter.endObject();
}
#SuppressWarnings("resource")
private static void writeField(final ResultSet resultSet, final JsonWriter jsonWriter, final ResultSetMetaData metaData, final int column)
throws SQLException, IOException {
final int columnType = metaData.getColumnType(column);
switch ( columnType ) {
case Types.BIT:
case Types.TINYINT:
case Types.SMALLINT:
throw new UnsupportedOperationException("TODO: " + JDBCType.valueOf(columnType));
case Types.INTEGER:
jsonWriter.value((Integer) resultSet.getObject(column));
break;
case Types.BIGINT:
case Types.FLOAT:
case Types.REAL:
case Types.DOUBLE:
case Types.NUMERIC:
case Types.DECIMAL:
case Types.CHAR:
throw new UnsupportedOperationException("TODO: " + JDBCType.valueOf(columnType));
case Types.VARCHAR:
jsonWriter.value((String) resultSet.getObject(column));
break;
case Types.LONGVARCHAR:
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.NULL:
case Types.OTHER:
case Types.JAVA_OBJECT:
case Types.DISTINCT:
case Types.STRUCT:
case Types.ARRAY:
case Types.BLOB:
case Types.CLOB:
case Types.REF:
case Types.DATALINK:
case Types.BOOLEAN:
case Types.ROWID:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.LONGNVARCHAR:
case Types.NCLOB:
case Types.SQLXML:
case Types.REF_CURSOR:
case Types.TIME_WITH_TIMEZONE:
case Types.TIMESTAMP_WITH_TIMEZONE:
throw new UnsupportedOperationException("TODO: " + JDBCType.valueOf(columnType));
default:
throw new UnsupportedOperationException("Unknown type: " + columnType);
}
}
Example of writing to System.out, but, of course, it can be written anywhere just supplying an appropriate OutputStream instance:
final JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(System.out))
resultSetToJsonArrayStream(resultSet, jsonWriter);
Similarly to ResultSet, JsonWriter must be closed as well.
I've written the above code for SQLite, but it should work for MySQL too. For example, the test database created and populated with the following SQL statements:
CREATE TABLE `table` (i NUMBER NOT NULL, s TEXT NOT NULL);
INSERT INTO `table` (i, s) VALUES (1, 'foo');
INSERT INTO `table` (i, s) VALUES (2, 'bar');
INSERT INTO `table` (i, s) VALUES (3, 'baz');
will result in
[{"i":1,"s":"foo"},{"i":2,"s":"bar"},{"i":3,"s":"baz"}]
for both object model and streaming approaches.

Spying method calls the actual Method

I am writing a JUnit with Mockito. But on the line
when(encryptDecryptUtil.getKeyFromKeyStore(any(String.class))).thenReturn(keyMock);
It calls the actual method, which is causing the test failure. Interesting point is that it directly makes the actual call at start of the test case when when()...thenReturn() statemnts gets executed. Can you please tell me how I can fix this? My test is as per below
#Test
public void testDecryptData_Success() throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
encryptDecryptUtil = spy(new EncryptDecryptUtil());
Key keyMock = Mockito.mock(Key.class);
when(encryptDecryptUtil.getKeyFromKeyStore(any(String.class))).thenReturn(keyMock);
String inputData = "TestMessage";
String version = GetPropValues.getPropValue(PublisherConstants.KEYSTORE_VERSION);
byte[] enCryptedValue= new byte[] {9,2,5,8,9};
Cipher cipherMock = Mockito.mock(Cipher.class);
when(Cipher.getInstance(any(String.class))).thenReturn(cipherMock);
when(cipherMock.doFinal(any(byte[].class))).thenReturn(enCryptedValue);
String encryptedMessage = encryptDecryptUtil.encryptData(inputData);
assert(encryptedMessage.contains(version));
assertTrue(!encryptedMessage.contains(inputData));
}
On the third line it self, it calls the actual method.
Main code is as per below.
public class EncryptDecryptUtil {
private String publicKeyStoreFileName =
GetPropValues.getPropValue(PublisherConstants.KEYSTORE_PATH);
private String pubKeyStorePwd = "changeit";
private static final String SHA1PRNG = "SHA1PRNG";
private static final String pubKeyAlias="jceksaes";
private static final String JCEKS = "JCEKS";
private static final String AES_PADDING = "AES/CBC/PKCS5Padding";
private static final String AES = "AES";
private static final int CONST_16 = 16;
private static final int CONST_0 = 0;
private static final String KEY_STORE = "aes-keystore";
private static final String KEY_STORE_TYPE = "jck";
private static final Logger logger = Logger.getLogger(KafkaPublisher.class);
public Key getKeyFromKeyStore( String keystoreVersion) {
KeyStore keyStore = null;
Key key = null;
try {
keyStore = KeyStore.getInstance(JCEKS);
FileInputStream stream = null;
stream = new FileInputStream(publicKeyStoreFileName+KEY_STORE+PublisherConstants.UNDERSCORE+keystoreVersion+PublisherConstants.DOT+KEY_STORE_TYPE);
keyStore.load(stream, pubKeyStorePwd.toCharArray());
stream.close();
key = keyStore.getKey(pubKeyAlias, pubKeyStorePwd.toCharArray());
} catch (KeyStoreException e) {
e.printStackTrace();
}
catch (FileNotFoundException e) {
logger.error("Error Inside getKeyFromKeyStore, Exception = " + e);
e.printStackTrace();
} catch (CertificateException e) {
logger.error("Error Inside getKeyFromKeyStore, Exception = " + e);
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
logger.error("Error Inside getKeyFromKeyStore, Exception = " + e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
logger.error("Error Inside getKeyFromKeyStore, Exception = " + e);
e.printStackTrace();
} catch (IOException e) {
logger.error("Error Inside getKeyFromKeyStore, Exception = " + e);
e.printStackTrace();
}
return key;
}
public String encryptData(String data) {
String keystoreVersion = GetPropValues.getPropValue(PublisherConstants.KEYSTORE_VERSION);
SecretKey secKey = new SecretKeySpec(getKeyFromKeyStore(keystoreVersion).getEncoded(), AES);
String base64EncodedEncryptedMsg = null;
Cipher cipher = null;
try { ------- Logic -------------------}
catch() { }
}
}
Have a look at the "Important gotcha on spying real objects" section of the Spy documentation.
Essentially, you cannot use the when(...).thenReturn(...) pattern with Spies, because as you have discovered, it calls the real method!
Instead, you use a different pattern which does exactly the same thing:
doReturn(...).when(spy).someMethod();
So, for your example:
doReturn(keyMock).when(encryptDecryptUtil).getKeyFromKeyStore(any(String.class));
Some advice which is unrelated to your question: If I read your code correctly, then EncryptDecryptUtil is the class that you are testing. As a general rule, you should not mock, stub, or spy on the object that you are actually testing, because then you are not testing the true object. You are actually testing a version of the object creating by the Mockito library. Furthermore, it's an uncommon pattern which will make your tests hard to read and maintain. If you find yourself having to do this, then the best thing would be to refactor your code so that the methods you are mocking (or spying on) and the methods you are testing are in different classes.

How can I deserialize JSON to a simple Dictionary<string,string> in ASP.NET?

I have a simple key/value list in JSON being sent back to ASP.NET via POST. Example:
{ "key1": "value1", "key2": "value2"}
I AM NOT TRYING TO DESERIALIZE INTO STRONGLY-TYPED .NET OBJECTS
I simply need a plain old Dictionary(Of String, String), or some equivalent (hash table, Dictionary(Of String, Object), old-school StringDictionary--hell, a 2-D array of strings would work for me.
I can use anything available in ASP.NET 3.5, as well as the popular Json.NET (which I'm already using for serialization to the client).
Apparently neither of these JSON libraries have this forehead-slapping obvious capability out of the box--they are totally focused on reflection-based deserialization via strong contracts.
Any ideas?
Limitations:
I don't want to implement my own JSON parser
Can't use ASP.NET 4.0 yet
Would prefer to stay away from the older, deprecated ASP.NET class for JSON
Json.NET does this...
string json = #"{""key1"":""value1"",""key2"":""value2""}";
var values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
More examples: Serializing Collections with Json.NET
I did discover .NET has a built in way to cast the JSON string into a Dictionary<String, Object> via the System.Web.Script.Serialization.JavaScriptSerializer type in the 3.5 System.Web.Extensions assembly. Use the method DeserializeObject(String).
I stumbled upon this when doing an ajax post (via jquery) of content type 'application/json' to a static .net Page Method and saw that the method (which had a single parameter of type Object) magically received this Dictionary.
System.Text.Json
This can now be done using System.Text.Json which is built-in to .NET Core 3.0. It's now possible to deserialize JSON without using third-party libraries.
var json = #"{""key1"":""value1"",""key2"":""value2""}";
var values = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
Also available in NuGet package System.Text.Json if using .NET Standard or .NET Framework.
Make sure to read and understand:
https://github.com/dotnet/runtime/issues/30452
For those searching the internet and stumbling upon this post, I wrote a blog post on how to use the JavaScriptSerializer class.
Read more...
http://procbits.com/2011/04/21/quick-json-serializationdeserialization-in-c/
Here is an example:
var json = "{\"id\":\"13\", \"value\": true}";
var jss = new JavaScriptSerializer();
var table = jss.Deserialize<dynamic>(json);
Console.WriteLine(table["id"]);
Console.WriteLine(table["value"]);
I had the same problem, so I wrote this my self. This solution is differentiated from other answers because it can deserialize in to multiple levels.
Just send JSON string in to deserializeToDictionary function it will return non strongly-typed Dictionary<string, object> object.
Old code
private Dictionary<string, object> deserializeToDictionary(string jo)
{
var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(jo);
var values2 = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> d in values)
{
// if (d.Value.GetType().FullName.Contains("Newtonsoft.Json.Linq.JObject"))
if (d.Value is JObject)
{
values2.Add(d.Key, deserializeToDictionary(d.Value.ToString()));
}
else
{
values2.Add(d.Key, d.Value);
}
}
return values2;
}
Ex: This will return Dictionary<string, object> object of a Facebook JSON response.
Test
private void button1_Click(object sender, EventArgs e)
{
string responsestring = "{\"id\":\"721055828\",\"name\":\"Dasun Sameera Weerasinghe\",\"first_name\":\"Dasun\",\"middle_name\":\"Sameera\",\"last_name\":\"Weerasinghe\",\"username\":\"dasun\",\"gender\":\"male\",\"locale\":\"en_US\", hometown: {id: \"108388329191258\", name: \"Moratuwa, Sri Lanka\",}}";
Dictionary<string, object> values = deserializeToDictionary(responsestring);
}
Note: hometown further deserilize into a Dictionary<string, object>
object.
Update
My old answer works great if there is no array on JSON string. This one further deserialize in to a List<object> if an element is an array.
Just send a JSON string in to deserializeToDictionaryOrList function it will return non strongly-typed Dictionary<string, object> object or List<object>.
private static object deserializeToDictionaryOrList(string jo,bool isArray=false)
{
if (!isArray)
{
isArray = jo.Substring(0, 1) == "[";
}
if (!isArray)
{
var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(jo);
var values2 = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> d in values)
{
if (d.Value is JObject)
{
values2.Add(d.Key, deserializeToDictionary(d.Value.ToString()));
}
else if (d.Value is JArray)
{
values2.Add(d.Key, deserializeToDictionary(d.Value.ToString(), true));
}
else
{
values2.Add(d.Key, d.Value);
}
}
return values2;
}else
{
var values = JsonConvert.DeserializeObject<List<object>>(jo);
var values2 = new List<object>();
foreach (var d in values)
{
if (d is JObject)
{
values2.Add(deserializeToDictionary(d.ToString()));
}
else if (d is JArray)
{
values2.Add(deserializeToDictionary(d.ToString(), true));
}
else
{
values2.Add(d);
}
}
return values2;
}
}
Tried to not use any external JSON implementation so i deserialised like this:
string json = "{\"id\":\"13\", \"value\": true}";
var serializer = new JavaScriptSerializer(); //using System.Web.Script.Serialization;
Dictionary<string, string> values = serializer.Deserialize<Dictionary<string, string>>(json);
I just needed to parse a nested dictionary, like
{
"x": {
"a": 1,
"b": 2,
"c": 3
}
}
where JsonConvert.DeserializeObject doesn't help. I found the following approach:
var dict = JObject.Parse(json).SelectToken("x").ToObject<Dictionary<string, int>>();
The SelectToken lets you dig down to the desired field. You can even specify a path like "x.y.z" to step further down into the JSON object.
If you're after a lightweight, no-added-references kind of approach, maybe this bit of code I just wrote will work (I can't 100% guarantee robustness though).
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
public Dictionary<string, object> ParseJSON(string json)
{
int end;
return ParseJSON(json, 0, out end);
}
private Dictionary<string, object> ParseJSON(string json, int start, out int end)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
bool escbegin = false;
bool escend = false;
bool inquotes = false;
string key = null;
int cend;
StringBuilder sb = new StringBuilder();
Dictionary<string, object> child = null;
List<object> arraylist = null;
Regex regex = new Regex(#"\\u([0-9a-z]{4})", RegexOptions.IgnoreCase);
int autoKey = 0;
for (int i = start; i < json.Length; i++)
{
char c = json[i];
if (c == '\\') escbegin = !escbegin;
if (!escbegin)
{
if (c == '"')
{
inquotes = !inquotes;
if (!inquotes && arraylist != null)
{
arraylist.Add(DecodeString(regex, sb.ToString()));
sb.Length = 0;
}
continue;
}
if (!inquotes)
{
switch (c)
{
case '{':
if (i != start)
{
child = ParseJSON(json, i, out cend);
if (arraylist != null) arraylist.Add(child);
else
{
dict.Add(key, child);
key = null;
}
i = cend;
}
continue;
case '}':
end = i;
if (key != null)
{
if (arraylist != null) dict.Add(key, arraylist);
else dict.Add(key, DecodeString(regex, sb.ToString()));
}
return dict;
case '[':
arraylist = new List<object>();
continue;
case ']':
if (key == null)
{
key = "array" + autoKey.ToString();
autoKey++;
}
if (arraylist != null && sb.Length > 0)
{
arraylist.Add(sb.ToString());
sb.Length = 0;
}
dict.Add(key, arraylist);
arraylist = null;
key = null;
continue;
case ',':
if (arraylist == null && key != null)
{
dict.Add(key, DecodeString(regex, sb.ToString()));
key = null;
sb.Length = 0;
}
if (arraylist != null && sb.Length > 0)
{
arraylist.Add(sb.ToString());
sb.Length = 0;
}
continue;
case ':':
key = DecodeString(regex, sb.ToString());
sb.Length = 0;
continue;
}
}
}
sb.Append(c);
if (escend) escbegin = false;
if (escbegin) escend = true;
else escend = false;
}
end = json.Length - 1;
return dict; //theoretically shouldn't ever get here
}
private string DecodeString(Regex regex, string str)
{
return Regex.Unescape(regex.Replace(str, match => char.ConvertFromUtf32(Int32.Parse(match.Groups[1].Value, System.Globalization.NumberStyles.HexNumber))));
}
[I realise that this violates the OP Limitation #1, but technically, you didn't write it, I did]
Mark Rendle posted this as a comment, I wanted to post it as an answer since it's the only solution that has worked so far to return the success and the error-codes json results from the Google reCaptcha response.
string jsonReponseString= wClient.DownloadString(requestUrl);
IDictionary<string, object> dict = new JavaScriptSerializer().DeserializeObject(jsonReponseString) as IDictionary<string, object>;
Thanks again, Mark!
For anyone who is trying to convert JSON to dictionary just for retrieving some value out of it. There is a simple way using Newtonsoft.JSON
using Newtonsoft.Json.Linq
...
JObject o = JObject.Parse(#"{
'CPU': 'Intel',
'Drives': [
'DVD read/writer',
'500 gigabyte hard drive'
]
}");
string cpu = (string)o["CPU"];
// Intel
string firstDrive = (string)o["Drives"][0];
// DVD read/writer
IList<string> allDrives = o["Drives"].Select(t => (string)t).ToList();
// DVD read/writer
// 500 gigabyte hard drive
I've added upon the code submitted by jSnake04 and Dasun herein. I've added code to create lists of objects from JArray instances. It has two-way recursion but as it is functioning on a fixed, finite tree model, there is no risk of stack overflow unless the data is massive.
/// <summary>
/// Deserialize the given JSON string data (<paramref name="data"/>) into a
/// dictionary.
/// </summary>
/// <param name="data">JSON string.</param>
/// <returns>Deserialized dictionary.</returns>
private IDictionary<string, object> DeserializeData(string data)
{
var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(data);
return DeserializeData(values);
}
/// <summary>
/// Deserialize the given JSON object (<paramref name="data"/>) into a dictionary.
/// </summary>
/// <param name="data">JSON object.</param>
/// <returns>Deserialized dictionary.</returns>
private IDictionary<string, object> DeserializeData(JObject data)
{
var dict = data.ToObject<Dictionary<String, Object>>();
return DeserializeData(dict);
}
/// <summary>
/// Deserialize any elements of the given data dictionary (<paramref name="data"/>)
/// that are JSON object or JSON arrays into dictionaries or lists respectively.
/// </summary>
/// <param name="data">Data dictionary.</param>
/// <returns>Deserialized dictionary.</returns>
private IDictionary<string, object> DeserializeData(IDictionary<string, object> data)
{
foreach (var key in data.Keys.ToArray())
{
var value = data[key];
if (value is JObject)
data[key] = DeserializeData(value as JObject);
if (value is JArray)
data[key] = DeserializeData(value as JArray);
}
return data;
}
/// <summary>
/// Deserialize the given JSON array (<paramref name="data"/>) into a list.
/// </summary>
/// <param name="data">Data dictionary.</param>
/// <returns>Deserialized list.</returns>
private IList<Object> DeserializeData(JArray data)
{
var list = data.ToObject<List<Object>>();
for (int i = 0; i < list.Count; i++)
{
var value = list[i];
if (value is JObject)
list[i] = DeserializeData(value as JObject);
if (value is JArray)
list[i] = DeserializeData(value as JArray);
}
return list;
}
Edit: This works, but the accepted answer using Json.NET is much more straightforward. Leaving this one in case someone needs BCL-only code.
It’s not supported by the .NET framework out of the box. A glaring oversight – not everyone needs to deserialize into objects with named properties. So I ended up rolling my own:
VB.NET:
<Serializable()> Public Class StringStringDictionary
Implements ISerializable
Public dict As System.Collections.Generic.Dictionary(Of String, String)
Public Sub New()
dict = New System.Collections.Generic.Dictionary(Of String, String)
End Sub
Protected Sub New(info As SerializationInfo, _
context As StreamingContext)
dict = New System.Collections.Generic.Dictionary(Of String, String)
For Each entry As SerializationEntry In info
dict.Add(entry.Name, DirectCast(entry.Value, String))
Next
End Sub
Public Sub GetObjectData(info As SerializationInfo, context As StreamingContext) Implements ISerializable.GetObjectData
For Each key As String in dict.Keys
info.AddValue(key, dict.Item(key))
Next
End Sub
End Class
same on C#:
public class StringStringDictionary : ISerializable
{
public System.Collections.Generic.Dictionary<string, string> dict;
public StringStringDictionary()
{
dict = new System.Collections.Generic.Dictionary<string, string>();
}
protected StringStringDictionary(SerializationInfo info, StreamingContext context)
{
dict = new System.Collections.Generic.Dictionary<string, string>();
foreach (SerializationEntry entry in info)
dict.Add(entry.Name, (string)entry.Value);
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
foreach (string key in dict.Keys)
info.AddValue(key, dict[key]);
}
}
Called with:
string MyJsonString = "{ \"key1\": \"value1\", \"key2\": \"value2\"}";
System.Runtime.Serialization.Json.DataContractJsonSerializer dcjs = new
System.Runtime.Serialization.Json.DataContractJsonSerializer(
typeof(StringStringDictionary));
System.IO.MemoryStream ms = new
System.IO.MemoryStream(Encoding.UTF8.GetBytes(MyJsonString));
StringStringDictionary myfields = (StringStringDictionary)dcjs.ReadObject(ms);
Response.Write("Value of key2: " + myfields.dict["key2"]);
Sorry for the mix of C# and VB.NET…
I added a check for null values in the JSON to the other answer
I had same problem so I wrote this my self. This solution is
differentiated from other answers because it can deserialize in to
multiple levels.
Just send json string in to deserializeToDictionary function it
will return non strongly-typed Dictionary<string, object> object.
private Dictionary<string, object> deserializeToDictionary(string jo)
{
var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(jo);
var values2 = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> d in values)
{
if (d.Value != null && d.Value.GetType().FullName.Contains("Newtonsoft.Json.Linq.JObject"))
{
values2.Add(d.Key, deserializeToDictionary(d.Value.ToString()));
}
else
{
values2.Add(d.Key, d.Value);
}
}
return values2;
}
Ex: This will return Dictionary<string, object> object of a Facebook
JSON response.
private void button1_Click(object sender, EventArgs e)
{
string responsestring = "{\"id\":\"721055828\",\"name\":\"Dasun Sameera
Weerasinghe\",\"first_name\":\"Dasun\",\"middle_name\":\"Sameera\",\"last_name\":\"Weerasinghe\",\"username\":\"dasun\",\"gender\":\"male\",\"locale\":\"en_US\",
hometown: {id: \"108388329191258\", name: \"Moratuwa, Sri Lanka\",}}";
Dictionary<string, object> values = deserializeToDictionary(responsestring);
}
Note: hometown further deserialize into a Dictionary<string, object> object.
It seems all of these answers here just assume you can get that little string out of a bigger object... for people looking to simply deserealize a large object with such a dictionary somewhere inside the mapping, and who are using the System.Runtime.Serialization.Json DataContract system, here's a solution:
An answer on gis.stackexchange.com had this interesting link. I had to recover it with archive.org, but it offers a pretty much perfect solution: a custom IDataContractSurrogate class in which you implement exactly your own types. I was able to expand it easily.
I made a bunch of changes in it, though. Since the original source is no longer available, I'll post the entire class here:
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
namespace JsonTools
{
/// <summary>
/// Allows using Dictionary<String,String> and Dictionary<String,Boolean> types, and any others you'd like to add.
/// Source: https://web.archive.org/web/20100317222656/my6solutions.com/post/2009/06/30/DataContractSerializer-DataContractJsonSerializer-JavaScriptSerializer-XmlSerializer-for-serialization.aspx
/// </summary>
public class JsonSurrogate : IDataContractSurrogate
{
/// <summary>
/// Deserialize an object with added support for the types defined in this class.
/// </summary>
/// <typeparam name="T">Contract class</typeparam>
/// <param name="json">JSON String</param>
/// <param name="encoding">Text encoding</param>
/// <returns>The deserialized object of type T</returns>
public static T Deserialize<T>(String json, Encoding encoding)
{
if (encoding == null)
encoding = new UTF8Encoding(false);
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(
typeof(T), new Type[0], int.MaxValue, true, new JsonSurrogate(), false);
using (MemoryStream stream = new MemoryStream(encoding.GetBytes(json)))
{
T result = (T)deserializer.ReadObject(stream);
return result;
}
}
// make sure all values in this are classes implementing JsonSurrogateObject.
private static Dictionary<Type, Type> KnownTypes =
new Dictionary<Type, Type>()
{
{typeof(Dictionary<String, String>), typeof(SSDictionary)},
{typeof(Dictionary<String, Boolean>), typeof(SBDictionary)}
};
#region Implemented surrogate dictionary classes
[Serializable]
public class SSDictionary : SurrogateDictionary<String>
{
public SSDictionary() : base() {}
protected SSDictionary (SerializationInfo info, StreamingContext context) : base(info, context) {}
}
[Serializable]
public class SBDictionary : SurrogateDictionary<Boolean>
{
public SBDictionary() : base() {}
protected SBDictionary (SerializationInfo info, StreamingContext context) : base(info, context) {}
}
#endregion
/// <summary>Small interface to easily extract the final value from the object.</summary>
public interface JsonSurrogateObject
{
Object DeserializedObject { get; }
}
/// <summary>
/// Class for deserializing any simple dictionary types with a string as key.
/// </summary>
/// <typeparam name="T">Any simple type that will be deserialized correctly.</typeparam>
[Serializable]
public abstract class SurrogateDictionary<T> : ISerializable, JsonSurrogateObject
{
public Object DeserializedObject { get { return dict; } }
private Dictionary<String, T> dict;
public SurrogateDictionary()
{
dict = new Dictionary<String, T>();
}
// deserialize
protected SurrogateDictionary(SerializationInfo info, StreamingContext context)
{
dict = new Dictionary<String, T>();
foreach (SerializationEntry entry in info)
{
// This cast will only work for base types, of course.
dict.Add(entry.Name, (T)entry.Value);
}
}
// serialize
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
foreach (String key in dict.Keys)
{
info.AddValue(key, dict[key]);
}
}
}
/// <summary>
/// Uses the KnownTypes dictionary to get the surrogate classes.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public Type GetDataContractType(Type type)
{
Type returnType;
if (KnownTypes.TryGetValue(type, out returnType))
{
return returnType;
}
return type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the object out of the surrogate datacontract object. This function is the reason all surrogate objects need to implement the JsonSurrogateObject class.
/// </summary>
/// <param name="obj">Result of the deserialization</param>
/// <param name="targetType">Expected target type of the deserialization</param>
/// <returns></returns>
public object GetDeserializedObject(object obj, Type targetType)
{
if (obj is JsonSurrogateObject)
{
return ((JsonSurrogateObject)obj).DeserializedObject;
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null;
}
#region not implemented
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
throw new NotImplementedException();
}
public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
#endregion
}
}
To add new supported types to the class, you just need to add your class, give it the right constructors and functions (look at SurrogateDictionary for an example), make sure it inherits JsonSurrogateObject, and add its type mapping to the KnownTypes dictionary. The included SurrogateDictionary can serve as basis for any Dictionary<String,T> types where T is any type that does deserialize correctly.
Calling it is really simple:
MyObjtype newObj = JsonSurrogate.Deserialize<MyObjtype>(jsonStr, encoding);
Note that for some reason this thing has trouble using key strings which contain spaces; they were simply not present in the final list. Might just be it's simply against json specs and the api I was calling was poorly implemented, mind you; I dunno. Anyway, I solved this by regex-replacing them with underscores in the raw json data and fixing the dictionary after the deserialization.
Based on comments above try JsonConvert.DeserializeObject<Dictionary<string,dynamic>>(json)
var json = #"{""key1"":1,""key2"":""value2"", ""object1"":{""property1"":""value1"",""property2"":[2,3,4,5,6,7]}}";
var parsedObject = JsonConvert.DeserializeObject<Dictionary<string,dynamic>>(json);
seems to work even for complex objects and lists.
My approach directly deserializes to IDictionary, without JObject or ExpandObject in between. The code uses converter, which is basically copied from ExpandoObjectConverter class found in JSON.NET sourcecode, but using IDictionary instead of ExpandoObject.
Usage:
var settings = new JsonSerializerSettings()
{
Converters = { new DictionaryConverter() },
};
var result = JsonConvert.DeserializeObject<IDictionary<string, object>>(json, settings);
Code:
// based on ExpandoObjectConverter, but using arrays instead of IList, to behave similar to System.Web.Script.Serialization.JavaScriptSerializer
public class DictionaryConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return ReadValue(reader);
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IDictionary<string, object>));
}
public override bool CanWrite
{
get { return false; }
}
private object ReadValue(JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment)
{
if (!reader.Read())
throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
}
switch (reader.TokenType)
{
case JsonToken.StartObject:
return ReadObject(reader);
case JsonToken.StartArray:
return ReadList(reader);
default:
if (IsPrimitiveToken(reader.TokenType))
return reader.Value;
throw JsonSerializationExceptionCreate(reader, string.Format(CultureInfo.InvariantCulture, "Unexpected token when converting IDictionary<string, object>: {0}", reader.TokenType));
}
}
private object ReadList(JsonReader reader)
{
List<object> list = new List<object>();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
default:
object v = ReadValue(reader);
list.Add(v);
break;
case JsonToken.EndArray:
return list;
}
}
throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
}
private object ReadObject(JsonReader reader)
{
IDictionary<string, object> dictionary = new Dictionary<string, object>();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
string propertyName = reader.Value.ToString();
if (!reader.Read())
throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
object v = ReadValue(reader);
dictionary[propertyName] = v;
break;
case JsonToken.Comment:
break;
case JsonToken.EndObject:
return dictionary;
}
}
throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
}
//based on internal Newtonsoft.Json.JsonReader.IsPrimitiveToken
internal static bool IsPrimitiveToken(JsonToken token)
{
switch (token)
{
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Undefined:
case JsonToken.Null:
case JsonToken.Date:
case JsonToken.Bytes:
return true;
default:
return false;
}
}
// based on internal Newtonsoft.Json.JsonSerializationException.Create
private static JsonSerializationException JsonSerializationExceptionCreate(JsonReader reader, string message, Exception ex = null)
{
return JsonSerializationExceptionCreate(reader as IJsonLineInfo, reader.Path, message, ex);
}
// based on internal Newtonsoft.Json.JsonSerializationException.Create
private static JsonSerializationException JsonSerializationExceptionCreate(IJsonLineInfo lineInfo, string path, string message, Exception ex)
{
message = JsonPositionFormatMessage(lineInfo, path, message);
return new JsonSerializationException(message, ex);
}
// based on internal Newtonsoft.Json.JsonPosition.FormatMessage
internal static string JsonPositionFormatMessage(IJsonLineInfo lineInfo, string path, string message)
{
if (!message.EndsWith(Environment.NewLine))
{
message = message.Trim();
if (!message.EndsWith(".", StringComparison.Ordinal))
message += ".";
message += " ";
}
message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
if (lineInfo != null && lineInfo.HasLineInfo())
message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
message += ".";
return message;
}
}
Annoyingly enough, if you want to use the default model binders, it looks like you will have to use numerical index values like a form POST.
See the following excerpt from this article http://msdn.microsoft.com/en-us/magazine/hh781022.aspx:
Though it’s somewhat counterintuitive, JSON requests have the same
requirements—they, too, must adhere to the form post naming syntax.
Take, for example, the JSON payload for the previous UnitPrice
collection. The pure JSON array syntax for this data would be
represented as:
[
{ "Code": "USD", "Amount": 100.00 },
{ "Code": "EUR", "Amount": 73.64 }
]
However, the default value providers and model binders require the
data to be represented as a JSON form post:
{
"UnitPrice[0].Code": "USD",
"UnitPrice[0].Amount": 100.00,
"UnitPrice[1].Code": "EUR",
"UnitPrice[1].Amount": 73.64
}
The complex object collection scenario is perhaps one of the most
widely problematic scenarios that developers run into because the
syntax isn’t necessarily evident to all developers. However, once you
learn the relatively simple syntax for posting complex collections,
these scenarios become much easier to deal with.
I just implemented this in RestSharp. This post was helpful to me.
Besides the code in the link, here is my code. I now get a Dictionary of results when I do something like this:
var jsonClient = new RestClient(url.Host);
jsonClient.AddHandler("application/json", new DynamicJsonDeserializer());
var jsonRequest = new RestRequest(url.Query, Method.GET);
Dictionary<string, dynamic> response = jsonClient.Execute<JObject>(jsonRequest).Data.ToObject<Dictionary<string, dynamic>>();
Be mindful of the sort of JSON you're expecting - in my case, I was retrieving a single object with several properties. In the attached link, the author was retrieving a list.
A bit late to the game, but non of the above solutions pointed me in the direction of a pure and simple .NET, no json.net solution. So here it is, ended up being very simple. Below a full running example of how it is done with standard .NET Json serialization, the example has dictionary both in the root object and in the child objects.
The golden bullet is this cat, parse the settings as second parameter to the serializer:
DataContractJsonSerializerSettings settings =
new DataContractJsonSerializerSettings();
settings.UseSimpleDictionaryFormat = true;
Full code below:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
namespace Kipon.dk
{
public class JsonTest
{
public const string EXAMPLE = #"{
""id"": ""some id"",
""children"": {
""f1"": {
""name"": ""name 1"",
""subs"": {
""1"": { ""name"": ""first sub"" },
""2"": { ""name"": ""second sub"" }
}
},
""f2"": {
""name"": ""name 2"",
""subs"": {
""37"": { ""name"": ""is 37 in key""}
}
}
}
}
";
[DataContract]
public class Root
{
[DataMember(Name ="id")]
public string Id { get; set; }
[DataMember(Name = "children")]
public Dictionary<string,Child> Children { get; set; }
}
[DataContract]
public class Child
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "subs")]
public Dictionary<int, Sub> Subs { get; set; }
}
[DataContract]
public class Sub
{
[DataMember(Name = "name")]
public string Name { get; set; }
}
public static void Test()
{
var array = System.Text.Encoding.UTF8.GetBytes(EXAMPLE);
using (var mem = new System.IO.MemoryStream(array))
{
mem.Seek(0, System.IO.SeekOrigin.Begin);
DataContractJsonSerializerSettings settings =
new DataContractJsonSerializerSettings();
settings.UseSimpleDictionaryFormat = true;
var ser = new DataContractJsonSerializer(typeof(Root), settings);
var data = (Root)ser.ReadObject(mem);
Console.WriteLine(data.Id);
foreach (var childKey in data.Children.Keys)
{
var child = data.Children[childKey];
Console.WriteLine(" Child: " + childKey + " " + child.Name);
foreach (var subKey in child.Subs.Keys)
{
var sub = child.Subs[subKey];
Console.WriteLine(" Sub: " + subKey + " " + sub.Name);
}
}
}
}
}
}
You could use Tiny-JSON
string json = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
IDictionary<string, string> dict = Tiny.Json.Decode<Dictionary<string, string>>(json);
I would suggest using System.Runtime.Serialization.Json that is part of .NET 4.5.
[DataContract]
public class Foo
{
[DataMember(Name = "data")]
public Dictionary<string,string> Data { get; set; }
}
Then use it like this:
var serializer = new DataContractJsonSerializer(typeof(List<Foo>));
var jsonParams = #"{""data"": [{""Key"":""foo"",""Value"":""bar""}] }";
var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonParams));
var obj = serializer.ReadObject(stream);
Console.WriteLine(obj);
Here is my solution with System.Text.Json. You get a json string for the nested objects which in own turn can be converted to needed type later on.
public static Dictionary<string,string> JsonToDictionary(this string json)
{
var objectValues = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
var stringValues = objectValues.Select(o => new KeyValuePair<string, string>(o.Key, o.Value?.ToString()));
return stringValues.ToDictionary(pair => pair.Key, pair => pair.Value);
}
Here is the usage example to fetch values from a nested object:
var result= json.JsonToDictionary()["outerField"]
.JsonToDictionary()["innerField"];
Note that this solution does not cover the json objects starting as an array like [12, 13]. These objects can be read as an array in the begining and then the extension method can be applied on each item, in case the items are complex objects with their own properties.