GWT HashMap to/from JSON - json

I might be getting a bit tired tonight but here it goes:
I'd like to have GWT HashMap to/from JSON. How would I achieve this?
In other words, I'd like to take an HashMap, take its JSON representation, store it somewhere and get it back to its native Java representation.

Here is my quick solution:
public static String toJson(Map<String, String> map) {
String json = "";
if (map != null && !map.isEmpty()) {
JSONObject jsonObj = new JSONObject();
for (Map.Entry<String, String> entry: map.entrySet()) {
jsonObj.put(entry.getKey(), new JSONString(entry.getValue()));
}
json = jsonObj.toString();
}
return json;
}
public static Map<String, String> toMap(String jsonStr) {
Map<String, String> map = new HashMap<String, String>();
JSONValue parsed = JSONParser.parseStrict(jsonStr);
JSONObject jsonObj = parsed.isObject();
if (jsonObj != null) {
for (String key : jsonObj.keySet()) {
map.put(key, jsonObj.get(key).isString().stringValue());
}
}
return map;
}

Not the most optimized, but should be easy to code: use JSONObject.
Iterate over your map's entries and put them in a JSONObject (converting each value to a JSONValue of the appropriate type), then call toString to get the JSON representation.
For parsing, get a JSONObject back using a JSONParser, then iterate over the keySet, geting the values and putting them in your map (after unwrapping the JSONValues)
But beware of the keys you use! You cannot use any kind of key as a property name in JS; and JSON processing in the browser always involve going through a JS object (or implementing the JSON parser yourself, which won't perform the same)

Related

Spring - Return Raw JSON without double serialization

I know there are other posts similar to this, but I haven't found any that help me find a solution for this particular case.
I am trying to return a HashMap<String, Object> from my Controller.
The Object part is a JSON string, but its being double serialized and not returned as a raw JSON string, thus not ending up with extra quotations and escape characters.
Controller function:
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public HashMap<String, Object> heartbeat(){
String streamInfo = service.getStreamInfo();
String streamCursorInfo = service.getStreamCursorInfo();
String topicInfo = service.getTopicInfo();
String greeting = "This is a sample app for using Spring Boot with MapR Streams.";
HashMap<String, Object> results = new HashMap();
results.put("greeting", greeting);
results.put("streamInfo", streamInfo);
results.put("streamCursorInfo", streamCursorInfo);
results.put("topicInfo", topicInfo);
return results;
}
Service function:
private String performCURL(String[] command){
StringBuilder stringBuilder = new StringBuilder();
try{
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process p = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while((line = reader.readLine()) != null){
stringBuilder.append(line);
}
}
catch(Exception e){
LOGGER.error(ExceptionUtils.getRootCauseMessage(e));
}
return stringBuilder.toString();
}
The cURL command I run already returns a raw JSON string. So im just trying to add it to the HashMap to be returned in the heartbeat response.
But every time I run this, my output looks like:
{
"greeting": "This is a sample app for using Spring Boot with MapR Streams.",
"streamCursorInfo": "{\"timestamp\":1538676344564,\"timeofday\":\"2018-10-04 02:05:44.564 GMT-0400 PM\",\"status\":\"OK\",\"total\":1,\"data\":[{\"consumergroup\":\"MapRDBConsumerGroup\",\"topic\":\"weightTags\",\"partitionid\":\"0\",\"produceroffset\":\"44707\",\"committedoffset\":\"10001\",\"producertimestamp\":\"2018-10-03T05:57:27.128-0400 PM\",\"consumertimestamp\":\"2018-09-21T12:35:51.654-0400 PM\",\"consumerlagmillis\":\"1056095474\"}]}",
...
}
If i return only the single string, such as streamInfo then it works fine and doesnt add the extra quotes and escape chars.
Can anyone explain what im missing or need to do to prevent this double serialization?
Instead of returning a HashMap, create an object like this:
public class HeartbeatResult {
private String greeting;
... //other fields here
#JsonRawValue
private String streamCursorInfo;
... //getters and setters here (or make the object immutable by having just a constructor and getters)
}
With #JsonRawValue Jackson will serialize the string as is. See https://www.baeldung.com/jackson-annotations for more info.
streamCursorInfo is a string, not an object => the serialization will escape the " character.
If you are able to return the object containing the data, it will work out of the box. If what you have is just a String, I suggest to serialize it to JsonNode and add it in your response
ObjectMapper objectMapper = new ObjectMapper();
JsonNode streamCursorInfo = objectMapper.readTree(service.getStreamInfo())
results.put("streamCursorInfo", streamCursorInfo);

How to extract the a value from a JSON object by matching the key in a Map using Java 8?

I'm using Java 8 and jackson to try and get an int value from a json object. Here is some similar code I used to verify the structure.
HashMap<String, Object> myMap = new HashMap<String, Object>();
myMap
.entrySet()
.stream()
.forEach(x -> System.out.println(x.getKey() + " => " + x.getValue()));
and the result:
myKey1 => {"number":1}
myKey2 => {"number":1}
myKey3 => {"number":2}
What I'm trying to do is use the key, like myKey1 to find the json value i.e. {"number":1} and pull out the actual number, which for that key is 1.
However, I don't all know the values of the key's, just the key I want to match up. So I have to do this dynamically. I know the structure of the values and that is always the same, except the number can be different.
I think because I don't know the keys and their order, plus I'm using entrySet, that is forcing me into using Optional, which is making this more difficult.
Here is my code where I'm actually using the key to pull the json value:
Optional<Object> object = myMap.entrySet().stream()
.filter(e -> e.getKey().equals(myKey))
.map(Map.Entry::getValue)
.findFirst();
However, the stream pulls back
Optional[{"number":1}]
and I can't seem to get the number out so I can return it from a method. I don't actually
have a Java class for the object, so I assume that is why it's returning Optional as I was getting a compile error without it, but I'm not sure.
Any ideas as to the right way to do this?
Why iterating over all the entries of the map and do a linear search by key, to get the value of the entry?
Just get the value directly:
Object value = myMap.get(myKey);
Now, with regard to the number inside value, as you say you don't have a class that represents the values of the map, the most likely thing is that the JSON library you're using is creating a Map for each value. So from now on, let's assume that the values are actually some implementation of Map:
Integer = null;
if (value != null) {
// What's the type of the value? Maybe a Map?
if (value instanceof Map) {
Map<String, Object> valueAsMap = (Map<String, Object>) value;
number = (Integer) valueAsMap.get("number");
}
}
I'm assuming the numbers are Integers, but they can perfectly be Long instances or even Doubles or BigDecimals. Just be sure of the exact type, so the second cast doesn't fail.
EDIT: For completeness, here's the code for when the values are not maps, but of some class that represents a json node. Here I'm using JsonNode from Jackson library, but the approach is very similar for other libs:
Integer = null;
if (value != null) {
if (value instanceof JsonNode) {
JsonNode valueAsNode = (JsonNode) value;
number = (Integer) valueAsNode.get("number").numberValue();
}
}
findFirst() returns an Optional. You need to call .get() or .orElse() or .orElseThrow() after you call findFirst().
You can then cast the Object to JsonNode and retrieve the value. Here is the full code:-
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> myMap = new HashMap<String, Object>();
myMap.put("myKey1", mapper.readTree("{\"number\":1}"));
myMap.put("myKey2", mapper.readTree("{\"number\":1}"));
myMap.put("myKey3", mapper.readTree("{\"number\":2}"));
System.out.println(getNumberUsingMapKey(myMap, "myKey3"));
}
private static int getNumberUsingMapKey(Map<String, Object> map, String key) throws Exception {
return Optional.of(map.get(key))
.map(o -> ((JsonNode) o).get("number").asInt())
.get(); //or use one of the following depending on your needs
// .orElse(-1);
// .orElseThrow(Exception::new);
}
//or use this method
private static int getNumberUsingMapKeyWithoutOptional(Map<String, Object> map, String key) throws Exception {
Object o = map.get(key);
return ((JsonNode) o).get("number").asInt();
}
Output
2
Unfortunately I didn't describe my problem statement well from the start, but this is the final working code that I was able to piece from the other answers.
This is what the data structure looked like. A key that had a value which was a JSON object that had a key and an int, in other words a nested JSON object.
Key: myKey1 Value:{"number":1}
Key: myKey2 Value:{"number":1}
Key: myKey3 Value:{"number":2}
I'm posting it in case some else runs into this use case. This may not be the best way to do it, but it works.
Object value = myMap.get(keyName);
ObjectMapper mapper = new ObjectMapper();
String jsonString = (String) value;
JsonNode rootNode = mapper.readTree(jsonString);
JsonNode numberNode = rootNode.path("number");
System.out.println("number: " + numberNode.intValue());
and the result:
number: 1

Converting associative array request parameters to JSON with Jackson

I am the recipient of a webhook POST that looks like this, which I have decoded for readability.
id=12345&event=this_event&payload[customer][name]=ABC Company&payload[customer][city]=New York&payload[service][name]=New Customer&payload[service][action]=New
Using Spring MVC, I can easily get this into a Map<String, Sting> that looks like this
{id=97659204, event=test, payload[customer][name]=ABC Company, payload[customer][city]=New York, payload[service][name]=New Customer, payload[service][action]=New}
I need to parse every parameter (or Map key) that starts with "payload" into a JSON object.
My desired output from parsing the request parameters that start with "payload" would look something like this
{
customer : {
name : "ABC Company",
city : "New York"
},
service : {
name : "New Customer",
action : "New"
}
}
With the final state being a call to Jackson's ObjectMapper to turn that JSON into a POJO.
Since I have no control over the data format begin sent to me, what is the best/correct option for parsing those request parameters into a JSON object?
Thanks.
I ended up writing a custom parser for the payload[][][] parameters being passed in. It uses regex matcher and then recursion to analyze each parameter, traverse a Map of 1...n Maps and then the resulting Map makes perfect JSON.
#RequestMapping(value = "/receiver", method = RequestMethod.POST)
#ResponseBody
public void receiver(#RequestParam Map<String, Object> requestBody) throws IOException {
Pattern pattern = Pattern.compile("\\[([^\\]]+)]");
Map<String, Object> payloadMap = new HashMap<>();
Matcher matcher = null;
List<String> levels = null;
for (String key : requestBody.keySet()) {
if (key.startsWith("payload")) {
matcher = pattern.matcher(key);
levels = new ArrayList<>();
while (matcher.find()) {
levels.add(matcher.group(1));
}
payloadMap = nestMap(payloadMap, levels.iterator(), (String) requestBody.get(key));
}
}
LOG.debug(mapper.writeValueAsString(payloadMap));
}
#SuppressWarnings("unchecked")
private Map<String, Object> nestMap(Map<String, Object> baseMap, Iterator<String> levels, String value) {
String key = levels.next();
if (!levels.hasNext()) {
baseMap.put(key, value);
} else {
if (!baseMap.containsKey(key)) {
baseMap.put(key, nestMap(new HashMap<String, Object>(), levels, value));
} else {
baseMap.put(key, nestMap((Map<String, Object>) baseMap.get(key), levels, value));
}
}
return baseMap;
}

How to ignore duplicate keys while parsing json using gson?

I am getting a a duplicate key exception while parsing JSON response containing timestamps as keys using GSON. It gives the following error:
com.google.gson.JsonSyntaxException: duplicate key: 1463048935
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:186)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:141)
How do I make it ignore the duplicate entries, and just parse it to a map with any one from the duplicate entries?
Hackerman solution, tested and working using GSON v2.8.5, but use at your own risk! Whenever you update GSON to a new version, make sure to check whether this is still working!
Basically, you can use the fact that the generic ObjectTypeAdapter ignores duplicates as seen here:
Looks like MapTypeAdapterFactory checks for duplicate
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
however ObjectTypeAdapter does not
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedTreeMap<String, Object>();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map;
What you can do now is trying to deserialize using fromJson as usual, but catch the "duplicate key" exception, deserialize as a generic Object, which will ignore duplicates, serialize it again, which will result in a JSON string without duplicate keys, and finally deserialize using the correct type it's actually meant to be.
Here is a Kotlin code example:
fun <T> String.deserialize(gson: Gson, typeToken: TypeToken<T>): T {
val type = typeToken.type
return try {
gson.fromJson<T>(this, type)
} catch (e: JsonSyntaxException) {
if (e.message?.contains("duplicate key") == true) {
gson.toJson(deserialize(gson, object : TypeToken<Any>() {})).deserialize(gson, typeToken)
} else {
throw e
}
}
}
Obviously, this adds (potentially heavy) overhead by requiring 2 deserializations and an additional serialization, but currently I don't see any other way to do this. If Google decides to add an option for a built-in way to ignore duplicates, as suggested here, better switch to that.
I couldn't use Kotlin (as answered before), so I adjusted it to Java
It could be achieved by registering the type adder:
#Test
void testDuplicatesIgnored() {
String json = "{\"key\": \"value\", \"key\": \"value2\"}";
Gson gson = new GsonBuilder()
.registerTypeAdapter(Map.class, new JsonDeserializer<Map<String, Object>>() {
#Override
public Map<String, Object> deserialize(JsonElement json1, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new Gson().fromJson(json1, typeOfT);
}
})
.create();
Type mapType = new TypeToken<Map<String, Object>>() {}.getType();
Map<String, Object> map = gson.fromJson(json, mapType);
System.out.println("map = " + map); // map = {key=value2}
assertThat(map).hasSize(1);
assertThat(map.get("key")).isEqualTo("value2");
}
This way all the mappings to Map.class will go through your deserializer code
Yea, looks like a dirty hack, but it works
Another way is to register type adder for your custom type to make the deserializer being called only where you need it:
#Test
void testDuplicatesIgnored() {
String json = "{\"key\": \"value\", \"key\": \"value2\"}";
Gson gson = new GsonBuilder()
.registerTypeAdapter(MapIgnoringDuplicatesContainer.class, new JsonDeserializer<MapIgnoringDuplicatesContainer>() {
#Override
public MapIgnoringDuplicatesContainer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Type mapType = new TypeToken<Map<String, Object>>() {}.getType();
return new MapIgnoringDuplicatesContainer(new Gson().fromJson(json, mapType));
}
})
.create();
Map<String, Object> map = gson.fromJson(json, MapIgnoringDuplicatesContainer.class).getMap();
System.out.println("map = " + map); // map = {key=value2}
assertThat(map).hasSize(1);
assertThat(map.get("key")).isEqualTo("value2");
}
private class MapIgnoringDuplicatesContainer {
private Map<String, Object> map;
public MapIgnoringDuplicatesContainer(Map<String, Object> map) {
this.map = map;
}
public Map<String, Object> getMap() {
return map;
}
}
```

Creating a JSON object in MonoTouch (C#)

How do i manually create a JSON object in monotouch(4.0)?
The System.JSON namespace is available, (JsonObject, JsonArray, jSonPrimitive and JsonValue) but those are all abstract, so i can't just do this :
JsonObject myObject = new JsonObject();
I need to manually build a JSON object, and do not want to use DataContractSerialisation.
For reference purposes :
-I will need to transfer that JSON object to a server with a web call later. (but i can handle that part)
Use JSON.net http://json.codeplex.com/
As it turns out, after a long time of trying and searching ; only the JsonPrimitiveconstructor and the JsonObject constructor can be used.
JsonPrimitive and JsonValue can both be cast to JsonValue.
the JsonObject requires a KeyValuePair<string, JsonValue>
if i define functions like this :
public static KeyValuePair<String, JsonValue> IntRequired(string key, int val)
{
return new KeyValuePair<String, JsonValue>(key, new JsonPrimitive(val));
}
i can create a json object like this :
JSonObject myObject = new JsonObject();
myObject.add(new IntRequired("id",1));