I have a Json String with duplicate values:
String json = "{\"Sign_In_Type\":\"Action\",\"Sign_In_Type\":\"Action\"}";
that correctly throws an exception when I try to create a JSONObject:
try {
JSONObject json_obj = new JSONObject(json);
String type = json_obj.getString("Sign_In_Type");
} catch (JSONException e) {
throw new RuntimeException(e);
}
Error:
Exception in thread "main" java.lang.RuntimeException: org.json.JSONException: Duplicate key "Sign_In_Type"
at com.campanja.app.Upload.main(Upload.java:52)
Caused by: org.json.JSONException: Duplicate key "Sign_In_Type"
at org.json.JSONObject.putOnce(JSONObject.java:1076)
at org.json.JSONObject.(JSONObject.java:205)
at org.json.JSONObject.(JSONObject.java:402)
at com.campanja.app.Upload.main(Upload.java:49)
Is there a smart way of removing or checking for duplicates before I convert it to a JSONOBject?
I have tried to create:
Set set = new HashSet(Arrays.asList(json));
but that gives me:
[{"Sign_In_Type":"Action","Sign_In_Type":"Action"}]
Any suggesstions welcome, thanks!
Two options I can think of right off the bat:
Parse the string using wither regex or tokens, add each key-value pair to a hashmap, and in the end recreate your JSON document with the duplicates removed. In this case though I would only remove key-value pairs that are exactly the same.
Download the source code for org.json.JSONObject , and make a slight modification to the code to automatically leave out duplicates. This is a bit dangerous though. Another option is to create a modified version that simply validates and modifies.
Extending JSONObject Working Example
The below code allows you to create a JSONOBbject with a string containing duplicate keys. Exceptions are thrown only when you have two key-values that have the same key, but different values. This was because I think it would be a problem to choose at random which of the two should be assigned (e.g. the later value?). Of course this can be changed to work as you wish (e.g. keep last value for multiple keys).
Modified Class
import org.json.JSONException;
import org.json.JSONObject;
public class JSONObjectIgnoreDuplicates extends JSONObject {
public JSONObjectIgnoreDuplicates(String json) {
super(json);
}
public JSONObject putOnce(String key, Object value) throws JSONException {
Object storedValue;
if (key != null && value != null) {
if ((storedValue = this.opt(key)) != null ) {
if(!storedValue.equals(value)) //Only through Exception for different values with same key
throw new JSONException("Duplicate key \"" + key + "\"");
else
return this;
}
this.put(key, value);
}
return this;
}
}
Main method
String json = "{\"Sign_In_Type\":\"Action\",\"Sign_In_Type\":\"Action\"}";
try {
JSONObject json_obj = new JSONObjectIgnoreDuplicates(json);
String type = json_obj.getString("Sign_In_Type");
} catch (JSONException e) {
throw new RuntimeException(e);
}
Assuming that
String json = "{\"Sign_In_Type\":\"Action\",\"Sign_In_Type\":\"Action\"}";
is a fiction for testing, can I ask whether creating the data as a String is the best choice in the first place? Why not a HashMap, or some other structure that either overwrites the subsequent reuses of a name or ignores them or throws an error when you add them? Don't wait until the conversion to JSON to make your data valid.
You can make use of the Jackson library to parse JSON. I'd problems doing the same task as you with org.json's package, but I turned to Jackson and I solved it: http://wiki.fasterxml.com/JacksonHome
I expanded Menelaos Bakopoulos answer, so that if inner values are also with duplicates, it won't create issues. the former solution worked on the first level only.
public class JSONObjectIgnoreDuplicates extends JSONObject {
public JSONObjectIgnoreDuplicates(JSONTokener x) throws JSONException {
super(x);
}
#Override
public JSONObject putOnce(String key, Object value) throws JSONException {
Object storedValue;
if (key != null && value != null) {
if ((storedValue = this.opt(key)) != null) {
if (!storedValue.toString().equals(value.toString())) //Only throw Exception for different values with same key
throw new JSONException("Duplicate key \"" + key + "\"");
else
return this;
}
this.put(key, value);
}
return this;
}
}
private class JsonDupTokener extends JSONTokener {
public JsonDupTokener(String s) {
super(s);
}
#Override
public Object nextValue() throws JSONException {
char c = this.nextClean();
switch (c) {
case '\"':
case '\'':
return this.nextString(c);
case '[':
this.back();
return new JSONArray(this);
case '{':
this.back();
return new JSONObjectIgnoreDuplicates(this);
default:
StringBuffer sb;
for (sb = new StringBuffer(); c >= 32 && ",:]}/\\\"[{;=#".indexOf(c) < 0; c = this.next()) {
sb.append(c);
}
this.back();
String string = sb.toString().trim();
if ("".equals(string)) {
throw this.syntaxError("Missing value");
} else {
return JSONObject.stringToValue(string);
}
}
}
}
Sorry I can't comment on Menelaos Bakopoulos' response due to reputation<50... Stupid system
Your solution unfortunately does not work here:
SEVERE: ERROR converting JSON to XML org.json.JSONException: Duplicate key "id"
org.json.JSONObject.putOnce(JSONObject.java:1076)
org.json.JSONObject.<init>(JSONObject.java:205)
org.json.JSONTokener.nextValue(JSONTokener.java:344)
org.json.JSONArray.<init>(JSONArray.java:125)
org.json.JSONTokener.nextValue(JSONTokener.java:348)
org.json.JSONObject.<init>(JSONObject.java:205)
JSONUtilities.JSONObjectIgnoreDuplicates.<init>(JSONUtilities.java:38)
It seems that calling super(json) in JSONObjectIgnoreDuplicates's constructor sends the code into a loop inside JSONObject, not JSONObjectIgnoreDuplicates ;{
I'm currently trying Asaf Bartov's solution, but there's no call from JSONObjectIgnoreDuplicates to JsonDupTokener, so appart from overloading the constructor of JSONObjectIgnoreDuplicates as follows, I don't see how it could work:
public JSONObjectIgnoreDuplicates(String json) throws JSONException {
this(new JSONDupTokener(json));
}
EDIT: I can confirm this works :))))
Thanks everybody!!!!
With Google Gson you can decide what to do with duplicates in the input string. You need to register your own TypeAdapter responsible for serialization/deserialization of objects. It would look like this:
// this implementation converts the json string to a Map<String, String>,
// saving only the first duplicate key and dropping the rest
class NoDuplicatesAdapter extends TypeAdapter<HashMap<String, String>> {
#Override
public void write(JsonWriter out, HashMap<String, String> value) throws IOException {
out.beginObject();
for (Map.Entry<String, String> e: value.entrySet()) {
out.name(e.getKey()).value(e.getValue());
}
out.endObject();
}
#Override
public HashMap<String, String> read(JsonReader in) throws IOException {
final HashMap<String, String> map = new HashMap<>();
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
// putting value to the map only if this key is not present;
// here you can actually find duplicate keys and decide what to do with them
map.putIfAbsent(name, in.nextString());
}
in.endObject();
return map;
}
}
Then you can parse your string:
String json = "{\"Sign_In_Type\":\"Action\",\"Sign_In_Type\":\"Action\"}";
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> map = new GsonBuilder()
.registerTypeAdapter(mapType, new NoDuplicatesAdapter())
.create()
.fromJson(str, mapType);
The map will contain only the first "Sign_In_Type".
Related
So, I have a value type :
class Session {
long createdAt;
List<String> postIds;
}
Using the jedis client(3.0.0-m1 is that matters), I am currently performing an hset to create the entries and hgetAll to retrieve all the key-values:
private redis.clients.jedis.Jedis jedis;
void createSession(String idAsKey, Map<String, String> hashFieldValues) {
jedis.hset(idAsKey, hashFieldValues);
}
Map<String, String> fetchSession(String idAsKey) {
return jedis.hgetAll(idAsKey);
}
The challenge that I am currently looking at is the ease of converting the Map<String, String> into the Session object. Is there an existing way to do this?
Server response for an equivalent command
1) "createdAt"
2) "1556099708307"
3) "postIds"
4) "[a, b, c]"
PS: Starting to learn Redis, hoping this kind of mapping might have already been solved for. Yes, not looking for a client change as an answer at least.
Jedis doesn't offer a way to map objects to hash structures.
If you are using spring, then you can look at HashMappers. A HashMapper converts a POJO to a hash and vice-versa. In your case, the HashMapper will convert a Session to a hash, and the other way round.
You are not using the fields separately, but simultaneously. Because of that, I'd suggest you to use plain and simple Redis Strings instead of using Redis Hashes. So you'd be using set to save entries and get to retrieve them.
Using above suggestions, your code may become as follows:
private redis.clients.jedis.Jedis jedis;
private com.google.gson.Gson gson; // see Note
void createSession(String idAsKey, Session object) {
String serializedValue = gson.toJson(object);
jedis.set(idAsKey, serializedValue);
}
Session fetchSession(String idAsKey) {
String serializedValue = jedis.get(idAsKey);
Session deserializedObject = gson.fromJson(serializedValue, Session.class);
return deserializedObject;
}
Note: I have used Gson for the purpose of serialization/deserialization. Needless to say, you can use any library.
You can convert the map to POJO
Session session = new ObjectMapper().convertValue(map, Session.class);
So you don't need a special handling expect using a mapper library as Jackson-Databind
You can save and fetch the data to and from Redis like below:
public Map<String, Object> saveDataInRedis(String id, Object obj) {
Map<String, Object> result = new HashMap<>();
String jsonObj = "";
try {
jsonObj = objectMapper.writeValueAsString(obj);
System.out.println(jsonObj);
} catch (JsonProcessingException jpe) {
logger.warn("In saveDataInRedis Exception :: "+jpe);
}
try {
valOps.set(id, jsonObj);
result.put(DataConstants.IS_SUCCESS, true);
result.put(DataConstants.MESSAGE, "Data saved succesfully in redis");
}catch(RedisConnectionFailureException e){
result =null;
logger.warn("In saveDataInRedis Exception e :: "+e);
}
System.out.println(valOps.getOperations().getClass());
System.out.println(jedisConnectionFactory.getPoolConfig().getMaxTotal());
return result;
}
Now get data from redis:
public Map<String, Object> getDataFromRedis(String id) {
Map<String, Object> result = new HashMap<>();
String jsonObj = valOps.get(id);
System.out.println("jsonObj :: " + jsonObj);
Session obj = null;
try {
obj = (Session) objectMapper.readValue(jsonObj, Session.class);
} catch (Exception e) {
result.put("data", null);
logger.warn("Data from redis is deleted");
logger.warn("In getDataFromRedis Exception e :: "+e);
}
if (obj != null) {
result.put(DataConstants.IS_SUCCESS, true);
result.put("data", obj);
}
System.out.println("result :: " + result);
return result;
}
I am working on a spring boot application and developing a spring rest (PATCH) service.
I have a situation where I need to map the data received on the PATCH request in a HashMap from the front end to a java POJO class.
The attributes that I need to map in the POJO class are present in the key of the Hashmap. We need to iterate through the map and associate the value of the key in the map to the POJO class attribute.
What is the best way to do this? I know that there is a very leborious way where I manually check each key in the map and associate the corresponding value to the appropriate setter in the POJO class. But this is extremely cumbersome if the number of attributes in the POJO class are many in number.
Is there an efficient way to achieve this?
Code snippet
#CrossOrigin
#RequestMapping(value="/{id}", method = RequestMethod.PATCH)
public Project editProject(#RequestBody Map<String, String> project, #PathVariable("id") String id) throws Exception {
logger.info("Inside editProject() API ");
if(project == null) {
return null;
}
for(String key: project.keySet()) {
logger.info("Keys passed for update ==> Key(" + key + "): Value(" + project.get(key) + ")");
}
Project projectRec = null;
try {
logger.info("updateProject() :: Before save :: ");
projectRec = projectService.updateProjectInfo(project, id);
logger.info("updateProject() :: After save :: Saved successfully ::: ", projectRec.toString());
}
catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
logger.info("Leaving createProject() API");
return projectRec;
}
public Project updateProjectInfo(Map<String, String> projectRec, String id) {
logger.info("Inside updateProjectInfo() API in ProjectServiceImpl");
Project dbRec = projectRepository.findOne(id);
setAttributesfromMaptoDBRec(projectRec, dbRec);
Project updatedProjectRec = null;
logger.info(" Leaving updateProjectInfo() API in ProjectServiceImpl");
return updatedProjectRec;
}
private void setAttributesfromMaptoDBRec(Map<String, String> updatedProjectRecMap, Project updatedProjectRec) {
logger.info(" Inside setAttributesfromMaptoDBRec() API in ProjectServiceImpl");
Set<String> nonUpdateableAttributes = ProjectServiceImpl.nonUpdateableKeys.get(ProjectServiceImpl.nonUpdateable);
if(updatedProjectRecMap == null) {
logger.warn(" Input Map to service is null in ProjectServiceImpl ===> " + (updatedProjectRecMap == null));
return;
}
for(String key: updatedProjectRecMap.keySet()) {
logger.info(" Project property ===> " + key);
logger.info(" nonUpdateableAttributes contains key ===> " + nonUpdateableAttributes.contains(key));
if(!nonUpdateableAttributes.contains(key)) {
try {
System.out.println("Field name: " + updatedProjectRec.getClass().getField(key));
System.out.println("HOW TO AUTOMATICALLY SET THE ATTRIBUTE TO THE POJO CLASS BASED ON THE KEY ??");
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
}
I have done similar thing long back using Jackson databind API.
Not sure, but this may be helpful:
import java.util.Map;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ObjectMarshalUtility {
public static <T> Map marshalObject(T obj){
ObjectMapper oMapper = new ObjectMapper();
oMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return oMapper.convertValue(obj, Map.class);
}
public static <T> T unMarshalObject(Class<T> classType, Map<String, Object> map){
ObjectMapper oMapper = new ObjectMapper();
oMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return oMapper.convertValue(map, classType);
}
}
For converting a DTO to a MAP:
Map<String,Object> searchInputMap=ObjectMarshalUtility.marshalObject(searchInputDTO);
For converting a Map to a DTO:
SearchInputDTO searchInputDTO=ObjectMarshalUtility.unMarshalObject(searchInputMap);
I have this JSON response, from a remote server and i really hope i can get help.
{
"data": {
"6111": {
"prereq": "0",
"mast": "The Master Tree"
},
"6112": {
"prereq": "1",
"mast": "Another Master Tree"
}
}
}
I use GSON to parse JSON, using the #SerializedName and #Exposeto obtain the value into a custom Model. But i do not understand how to get past the
"6111"
"6112"
I have checked other questions via the gson tag, to no avail.
try this
Iterator<String> iter = json.keys();
while (iter.hasNext()) {
String key = iter.next();
try {
Object value = json.get(key);
} catch (JSONException e) {
// Something went wrong!
}
}
///////////////////update////////////////////
JSONObject issueObj = new JSONObject(jsonContent);
Iterator iterator = issueObj.keys();
while(iterator.hasNext()){
String key = (String)iterator.next();
JSONObject issue = issueObj.getJSONObject(key);
// get id from issue
String _pubKey = issue.optString("id");
}
If you're using Gson, any time you have an object with keys you don't know ahead of time, you can just use Map instead of a custom object.
In this case, each element of the Map will be some "known" data structure, so you would use Map<String, MyObject>.
Your top-level class:
public class MyResponse {
#SerializedName("data")
#Expose
private Map<String, MyObject> data;
...
}
And your map's value class:
public class MyObject {
#SerializedName("prereq")
#Expose
private String prereq;
#SerializedName("mast")
#Expose
private String mast;
...
}
In the specific case of the json text you posted, you would then be able to use these objects like this:
response.getData().get("6111").getMast();
But you can also do anything you could normally do with a Map:
Map<String, MyObject> data = response.getData();
for (String key: data.keySet() {
...
}
for (MyObject obj : data.values()) {
...
}
Help me, guys!
Please, advise a simple solution to convert type boolean from server to int in Android :)
When I log in, i get respone from server like this :
{"status":{"error":0,"code":200,"message":"OK"},"response":{"profile":{"id":114,"username":"k#gmail.com","full_name":"k","phone":"9999999","verified":1,"admin":0,"allow_dev":false,"company":{"id":9,"name":"ООО \"Фингерз медиа\"","email":"info#fingers.by","sample":null,"logo":"http://storage.guardian-glass.fingersmedia.by/0cb56968b3cec1bba301db8d51d1015e.jpg"}},"access_token":"15629e234e04a54a5a44ef2aa4eccb1d"}}
Then I get undefined exception: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected NUMBER but was BOOLEAN
This happens because of JsonElement "allow_dev" is boolean from server, and in Android I have "allow_dev" like int.
This is login method :
private void login(String email, String pass) {
showProgress();
JsonObject params = new JsonObject();
params.addProperty("username", email);
params.addProperty("password", pass);
UserOperationsTask task = new UserOperationsTask(UserOperationsTask.TaskMode.MODE_LOGIN, params) {
#Override
public void onLoadFinished(Bundle res) {
hideProgress();
String errorMessage = res.getString(UserOperationsTask.RESULT_ERROR_STRING);
if (errorMessage != null) {
showMessage(getString(R.string.login_error), getString(R.string.server_request_error));
} else {
String json = res.getString(UserOperationsTask.RESULT_JSON_STRING);
if (json != null) {
JsonParser parser = new JsonParser();
JsonObject responseData = parser.parse(json).getAsJsonObject();
JsonObject companyObj = responseData.getAsJsonObject("profile").getAsJsonObject("company");
}
setRegisteredMode();
}
}
};
task.execute(this);
}
This method parse response and I tried to convert allow_dev type from boolean to int, but I dont understand whether I'm doing right?
private Bundle parseProfileResponse(Context context, JsonObject responseData) {
Log.d(TAG, "parseProfileResponse");
// I tried convert allow_dev type from boolean to int
String allow_dev_server = String.valueOf(responseData.get("allow_dev"));
boolean b = allow_dev_server.equals("true");
int allow_dev = b ? 1 : 0; // true == 1
Profile profile;
profile = GsonHolder.getGSONInstance().fromJson(responseData.getAsJsonObject("profile"), Profile.class);
profile.allow_dev = allow_dev;
Bundle res = new Bundle();
res.putParcelable(RESULT_OBJ, profile);
res.putString(RESULT_JSON_STRING, responseData.toString());
try {
Cache.saveToCache(context, profile);
} catch (RemoteException e) {
Log.d(TAG, "parseAuthResponse RemoteException: " + e.toString());
res.putString(RESULT_ERROR_STRING, context.getString(R.string.database_error));
} catch (OperationApplicationException e) {
Log.d(TAG, "parseAuthResponse OperationApplicationException: " + e.toString());
res.putString(RESULT_ERROR_STRING, context.getString(R.string.database_error));
}
return res;
}
I have to get "allow_dev" convert it in int and write to database.
If you can switch to mappings, you can use everything static typing can give you, comparing to weakly "typed" JsonElement and its subclasses. It has several advantages: compile-time checking, more robust code, IDE support, etc. The major disadvantage is that you have to write custom mappings, however you there tools (online as well) that can try to generate simple mapping classes based on the given sample JSON (for example, a very popular tool here: http://www.jsonschema2pojo.org/).
Now, let's create some mappings. The mappings like the ones below are used rarely: final fields (used for "server responses" that are no supposed to be modified programmatically; Gson can assign such fields anyway); null for non-primitives and some hacks for primitive type defaults values to cheat the compiler (like Integer.value(0) but not simply 0: otherwise, javac may inline constants, thus Gson cannot affect them); no getters/setters (data-transfer objects are just data bags, but yes, getters can work better). Anyway, you can use your style, and the followings mappings are used for demonstration purposes (the mappings code has even compact formatting: one property per line collapsing the annotations).
final class Response<T> {
final Status status = null;
final T response = null;
}
final class Status {
final int error = Integer.valueOf(0);
final int code = Integer.valueOf(0);
final String message = null;
}
final class ProfileAndAccessToken {
final Profile profile = null;
#SerializedName("access_token") final String accessToken = null;
}
final class Profile {
final int id = Integer.valueOf(0);
final String username = null;
#SerializedName("full_name") final String fullName = null;
final String phone = null;
final int verified = Integer.valueOf(0);
final int admin = Integer.valueOf(0);
#SerializedName("allow_dev") #JsonAdapter(BooleanToIntTypeAdapter.class) final int allowDev = Integer.valueOf(0);
final Company company = null;
}
final class Company {
final int id = Integer.valueOf(0);
final String name = null;
final String email = null;
final String sample = null;
final URL logo = null;
}
Note two annotations above:
#SerializedName -- this annotation can "rename" fields so you can use even special characters (however it's discouraged, and it's typically used to map incoming JSON property names to javaCamelNamingConventions).
#JsonAdapter -- this annotation can "attach" a special type adapter to a certain field, so that it could convert JSON properties to the given field and vice versa.
Now let's implement a type adapter that can convert incoming boolean values to local int values and vice versa. Note that type adapters work in streaming manner, so you have to read JSON tokens stream on fly during read, and, of course, generate JSON tokens stream during write.
final class BooleanToIntTypeAdapter
extends TypeAdapter<Integer> {
// Public constructors may be evil, and let expose as less as possible
// Gson can still instantiate this type adapter itself
private BooleanToIntTypeAdapter() {
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final Integer value)
throws IOException {
// If the given value is null, we must write the `null` token to the output JSON tokens stream anyway in order not to break JSON documents
if ( value == null ) {
out.nullValue();
return;
}
// Let's assume that we can accept either 0 or 1 that are mapped to false and true respectively
switch ( value ) {
case 0:
out.value(false);
break;
case 1:
out.value(true);
break;
default:
// Or throw an exception as fast as we can
throw new IllegalArgumentException("Cannot convert " + value + " to a boolean literal");
}
}
#Override
public Integer read(final JsonReader in)
throws IOException {
// Peek the next token type, and if it's null, then return null value too
if ( in.peek() == NULL ) {
return null;
}
// Otherwise parse the next token as boolean and map it either to 1 or 0
return in.nextBoolean() ? 1 : 0;
}
}
That's all you need in Gson. Now, for you entire JSON, since the response mapping class is generic, you have to tell Gson what the T is. Gson accepts java.lang.reflect.Type in the fromJson method, and this type can hold both raw and parameterized types, so Gson could (de)serialize more accurately.
private static final Type profileAndAccessTokenResponse = new TypeToken<Response<ProfileAndAccessToken>>() {
}.getType();
final Response<ProfileAndAccessToken> response = gson.fromJson(JSON, profileAndAccessTokenResponse);
System.out.println(response.response.profile.allowDev);
System.out.println(gson.toJson(response, profileAndAccessTokenResponse));
Output:
0
{"status":{"error":0,"code":200,"message":"OK"},"response":{"profile":{"id":114,"username":"k#gmail.com","full_name":"k","phone":"9999999","verified":1,"admin":0,"allow_dev":false,"company":{"id":9,"name":"ООО \"Фингерз медиа\"","email":"info#fingers.by","logo":"http://storage.guardian-glass.fingersmedia.by/0cb56968b3cec1bba301db8d51d1015e.jpg"}},"access_token":"15629e234e04a54a5a44ef2aa4eccb1d"}}
Note that the first line is 0: this what is generated with BooleanToIntTypeAdapter. Backing to your code:
String allow_dev_server = String.valueOf(responseData.get("allow_dev"));
boolean b = allow_dev_server.equals("true");
int allow_dev = b ? 1 : 0; // true == 1
Profile profile;
profile = GsonHolder.getGSONInstance().fromJson(responseData.getAsJsonObject("profile"), Profile.class);
profile.allow_dev = allow_dev;
Can be replaced with simple:
final Profile profile = GsonHolder.getGSONInstance().fromJson(responseData.getAsJsonObject("profile"), Profile.class)
// now `Profile.allowDev` is 0 or 1 automatically
Note that responseData can be replace with a particular mapping, so you could even not parse at that line: probably you might simply pass the whole response object as a class mapping rather that JsonObject in your parseProfileResponse -- it would be more robust.
I have a Json string which has a string message field.
String:
{ "Type" : "Text",
"Subject" : "data received",
"Message" :"{\\"language\\":\\"US\\",\\"data\\":\\"signature\\"}"
}
I want to convert it into the following structure:
Notification.java
public class Notification {
String type;
String subject;
Message message;
}
Message.java
public class Message {
String language;
String data;
}
Is there a way in which I can directly convert the string to a Java object of the above structure? I want to avoid deserializing twice.
You can create a custom Deserializer to deserialize the Message text into Message object and annotate the Message class with #JsonDeserialize:
#JsonDeserialize(using = MessageDeserializer.class)
public class Message {
String language;
String data;
}
public class MessageDeserializer extends JsonDeserializer<Message> {
public MessageDeserializer() {
super();
}
#Override
public Message deserialize(
final JsonParser jsonParser, final DeserializationContext deserializationContext) throws
IOException, JsonProcessingException {
final String messageText = jsonParser.getText();
// parse messageText into Message object
}
}
I am not sure my solution is acceptable since it does require additional explicit call to ObjectMapper to perform deserialization of the string value of Message.
However, this is it is done during the buildup of Notification object and does not require a String message property.
You need to add a ctor with String argument to Message class, where you can deserialize the String into Map and extract the instance propertieds:
public Message(String str) {
try {
#SuppressWarnings("unchecked")
Map<String, Object> map =
(Map<String, Object>)new ObjectMapper().readValue(str, Map.class);
language = map.containsKey("language") ? map.get("language").toString() : null ;
data = map.containsKey("data") ? map.get("data").toString() : null ;
} catch (Exception e) {
e.printStackTrace();
}
}
the new ctor will be called by Jackson when you deserialize a Notification object:
Notification n = (Notification)new ObjectMapper().readValue(reader, Notification.class);
You can convert json string into key-value pairs in Map.You will have to do twice as the Message value is again a json string.Use org.json for JSONObject
Map<String, String> map = new HashMap<String, String>();
JSONObject j = new JSONObject(str);
Iterator<String> keys = j.keys();
while( keys.hasNext() ){
String key = (String)keys.next();
String val = j.getString(key);
map.put(key, val);}
Then retrieve the values by iterating over the keys and pass the values into the class constructor
Then map.get(key) can be used to retrieve the values and will be passed into constructors of the classes.
The org.json library is easy to use:
//Create Json object to parse string
// str is input string
JSONObject obj = new JSONObject(str);
//Create Message
Message mess = new Message();
JSONObject obj2 = new JSONObject(obj.getString("Message"));
mess.data = obj2.getString("data");
mess.language = obj2.getString("language");
//Create Notification
Notification noti = new Notification();
noti.message = mess;
noti.subject = obj.getString("Subject");
noti.type = obj.getString("Type");