I want to convert all keys of JSON string/object to UPPERCASE in Java. The JSON can be nested.
I tried setting FieldNamingPolicy.UPPER_CAMEL_CASE in GsonBuilder but I guess that just works for String to JAVA Object and not for String to String.
String payload = "{\"key\" : {\"key1\" : \"value1\",\"key2\" : \"value2\"}}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(myCustomTypeAdapterFactory);
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
Gson gson = gsonBuilder.create();
Map mapDeserialized = gson.fromJson(payload, Map.class);
System.out.println("Map " + mapDeserialized);
There are other solutions through JACKSON with custom TypeAdapterFactory but those only work for one level and not for nested.
{"key" : {
"key1" : "value1",
"key2" : "value2"
}}
to
{"KEY" : {
"KEY1" : "value1",
"KEY2" : "value2"
}}
As you said FieldNamingPolicy is applied only for bean fields not for map keys. However UPPER_CAMEL_CASE is not what you want, it is camel case with first letter capitalized (SometingLikeThis). You have to implement your own deserializer that would do that for your:
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class UpperCaseAdapter implements JsonSerializer<Map<String, Object>>, JsonDeserializer<Map<String, Object>> {
public static final Type TYPE = new TypeToken<Map<String, Object>>() {}.getType();
#Override
public JsonElement serialize(Map<String, Object> src, Type typeOfSrc, JsonSerializationContext context) {
// TODO implement serialization if needed
return null;
}
#Override
public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String, Object> map = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
Object value = null;
if (entry.getValue().isJsonPrimitive()) {
value = entry.getValue().getAsString();
} else if (entry.getValue().isJsonObject()) {
value = context.deserialize(entry.getValue(), TYPE); // deserialize the object using the same type
} else if (entry.getValue().isJsonArray()) {
// TODO implement deserailization of array
} else if (entry.getValue().isJsonNull()) {
// skip nulls
continue;
}
map.put(entry.getKey().toUpperCase(), value); //toUpperCase() is what we want
}
return map;
}
}
you can use the adapter then:
String payload = "{\"key\" : {\"key1\" : \"value1\",\"key2\" : \"value2\"}, \"key3\": \"value\"}";
Gson gson = new GsonBuilder()
.registerTypeAdapter(UpperCaseAdapter.TYPE, new UpperCaseAdapter())
.create();
Map<String, Object> mapDeserialized = gson.fromJson(payload, UpperCaseAdapter.TYPE);
System.out.println("Map " + mapDeserialized);
and the output is:
Map {KEY3=value, KEY={KEY2=value2, KEY1=value1}}
Related
I have created a UpperCaseAdapter to upperCase all the keys of a json ,
But not working as expected
Here is my UpperCaseAdapter code , i want to also upper case the arrays part but it is not the case for me , how could correct that ?
public static class UpperCaseAdapter implements JsonSerializer<Map<String, Object>>, JsonDeserializer<Map<String, Object>> {
public static final Type TYPE = new TypeToken<Map<String, Object>>() {}.getType();
#Override
public JsonElement serialize(Map<String, Object> src, Type typeOfSrc, JsonSerializationContext context) {
// TODO implement serialization if needed
return null;
}
#Override
public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String, Object> map = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
Object value = null;
if (entry.getValue().isJsonPrimitive()) {
value = entry.getValue().getAsString();
} else if (entry.getValue().isJsonObject()) {
value = context.deserialize(entry.getValue(), TYPE); // deserialize the object using the same type
} else if (entry.getValue().isJsonArray()) {
// TODO implement deserailization of array
} else if (entry.getValue().isJsonNull()) {
// skip nulls
continue;
}
map.put(entry.getKey().toUpperCase(), value); //toUpperCase() is what we want
}
return map;
}
}
and this code is to call my UpperCaseAdapter
String payload = input_row.body;
Gson gson = new GsonBuilder()
.registerTypeAdapter(json_routine.UpperCaseAdapter.TYPE, new
json_routine.UpperCaseAdapter())
.create();
Map<String, Object> mapDeserialized = gson.fromJson(payload,
json_routine.UpperCaseAdapter.TYPE);
System.out.println("**** body_WS" + gson.toJson(mapDeserialized));
My body looks like this :
{
"transactionId": 181,
"ExtWareHouseId ": "toto",
"OwnerCode": "toto",
"ClientCode": "toto",
"activityId": toto,
"taskTypeCode": "01",
"activated": "2023-02-06T17:33:48.1905172+01:00",
"StartDate ": "2023-01-24T15:19:12.8591383+01:00",
"userCode": "toto",
"equipmentId": "01",
"PortId": "1",
"printerName": "",
"shipments": [
{
"shipmentId": 2,
"picklistId": "Example",
"ExtOrderId": "23-127",
"boxType": "120"
}
]
}
I get the following result :
{
"EXTWAREHOUSEID ":"toto",
"PRINTERNAME":"",
"TRANSACTIONID":"181",
"OWNERCODE":"toto",
"ACTIVITYID":"1072",
"EQUIPMENTID":"01",
"USERCODE":"toto",
"STARTDATE ":"2023-01-24T15:19:12.8591383+01:00",
"ACTIVATED":"2023-02-06T17:33:48.1905172+01:00",
"TASKTYPECODE":"01",
"CLIENTCODE":"toto",
"PORTID":"1"
}
The shipments part is missing , how could i correct my adapter to get a correct result
Any help on this , i'm stack on how to Upper case Array keys
I have an enhanced question regarding Flatten a JSON string to Map using Gson or Jackson.
My scenario included duplicated keys, so the solution in the above question will cause some duplicated keys overwritten. So I am thinking to construct keys by combining each level's key together.
So how to achieve that?
For example:
{
"id" : "123",
"name" : "Tom",
"class" : {
"subject" : "Math",
"teacher" : "Jack"
}
}
I want to get the Map:
"id" : "123",
"name" : "Tom",
"class.subject" : "Math",
"class.teacher" : "Jack"
************************Update Solution*************************************
Based on #Manos Nikolaidis's answer, I am able to achieve the following solution by considering ArrayNode.
public void processJsonString(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = (ArrayNode) mapper.readTree(jsonString);
processArrayNode(arrayNode);
}
private void processObjectNode(JsonNode jsonNode) {
Map<String, String> result = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields();
iterator.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
}
private void processArrayNode(ArrayNode arrayNode) {
for (JsonNode jsonNode : arrayNode) {
processObjectNode(jsonNode);
}
}
private void mapAppender(Map<String, String> result, Map.Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, node.getValue().asText());
} else if (node.getValue().isArray()) {
processArrayNode((ArrayNode) node.getValue());
} else if (node.getValue().isNull()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, null);
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
You can get the JSON as JsonNode and go through all fields recursively and add key and value field to a Map. When a value is an object instead of string you can add the field name to List to be joined with periods when a string is finally encountered. First create (for readability) a separate method that add Json fields to a Map:
void mapAppender(Map<String, String> result, Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(joining("."));
result.put(name, node.getValue().asText());
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
and use it like this:
ObjectMapper mapper = new ObjectMapper();
Map<String, String> result = new HashMap<>();
mapper.readTree(json).fields()
.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
Where fields() returns an Iterator. Beware of StackOverflowErrors and perhaps low performance for deeply nested JSON.
I resolved this using below simple code, Only think is need to download jettison and flattener.JsonFlattener library
import java.util.Map;
import org.codehaus.jettison.json.JSONObject;
import com.github.wnameless.json.flattener.JsonFlattener;
public class test {
public static void main(String[] args) {
String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
JSONObject jsonObject = new JSONObject();
String flattenedJson = JsonFlattener.flatten(jsonString);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(jsonString);
System.out.println(flattenedJsonMap);
}
}
Reference link : https://github.com/wnameless/json-flattener
I have an enhanced question regarding Flatten a JSON string to Map using Gson or Jackson.
My scenario included duplicated keys, so the solution in the above question will cause some duplicated keys overwritten. So I am thinking to construct keys by combining each level's key together.
So how to achieve that?
For example:
{
"id" : "123",
"name" : "Tom",
"class" : {
"subject" : "Math",
"teacher" : "Jack"
}
}
I want to get the Map:
"id" : "123",
"name" : "Tom",
"class.subject" : "Math",
"class.teacher" : "Jack"
************************Update Solution*************************************
Based on #Manos Nikolaidis's answer, I am able to achieve the following solution by considering ArrayNode.
public void processJsonString(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = (ArrayNode) mapper.readTree(jsonString);
processArrayNode(arrayNode);
}
private void processObjectNode(JsonNode jsonNode) {
Map<String, String> result = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields();
iterator.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
}
private void processArrayNode(ArrayNode arrayNode) {
for (JsonNode jsonNode : arrayNode) {
processObjectNode(jsonNode);
}
}
private void mapAppender(Map<String, String> result, Map.Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, node.getValue().asText());
} else if (node.getValue().isArray()) {
processArrayNode((ArrayNode) node.getValue());
} else if (node.getValue().isNull()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, null);
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
You can get the JSON as JsonNode and go through all fields recursively and add key and value field to a Map. When a value is an object instead of string you can add the field name to List to be joined with periods when a string is finally encountered. First create (for readability) a separate method that add Json fields to a Map:
void mapAppender(Map<String, String> result, Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(joining("."));
result.put(name, node.getValue().asText());
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
and use it like this:
ObjectMapper mapper = new ObjectMapper();
Map<String, String> result = new HashMap<>();
mapper.readTree(json).fields()
.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
Where fields() returns an Iterator. Beware of StackOverflowErrors and perhaps low performance for deeply nested JSON.
I resolved this using below simple code, Only think is need to download jettison and flattener.JsonFlattener library
import java.util.Map;
import org.codehaus.jettison.json.JSONObject;
import com.github.wnameless.json.flattener.JsonFlattener;
public class test {
public static void main(String[] args) {
String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
JSONObject jsonObject = new JSONObject();
String flattenedJson = JsonFlattener.flatten(jsonString);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(jsonString);
System.out.println(flattenedJsonMap);
}
}
Reference link : https://github.com/wnameless/json-flattener
I need to parse such kind of JSON:
{
"commodities": {
"39": "GOLD",
"41": "SILVER",
"42": "PLATINUM-APR16",
"85": "SUGAR (11) ",
"108": "WHEAT",
"116": "OIL-MAR16 (WTI CRUDE)",
"130": "CORN ",
"158": "COFFEE ",
"180": "ORANGE S.A.",
"282": "GOLD/JPY",
"304": "GOLD/EUR",
"332": "GOLD/TRY",
"468": "CRB INDEX",
"508": "COPPER",
...and a LOT more...
},
"currencies": {
"2": "USD/JPY",
"35": "AUD/USD",
"38": "USD/ILS",
...and a LOT more...
},
How is it possible to save this JSON to Map? So I could use it like this:
String value = mapCommodities.get(key);
String value = mapCommodities.get(39) //value equals "GOLD"
The problem is I don't know how to parse this index tag from JSON as integer value. I think it's needed to write custom Deserealizer but not really have an idea how.
create a custom deserializer
public class CustomDeserializer implements JsonDeserializer<List<Map<Integer, String>>>{
#Override
public List<Map<Integer, String>> deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<Map<Integer, String>> randomList = new ArrayList<>();
JsonObject parentJsonObject = element.getAsJsonObject();
Map<Integer, String> childMap;
for(Map.Entry<String, JsonElement> entry : parentJsonObject.entrySet()){
childMap = new HashMap<>();
for(Map.Entry<String, JsonElement> entry1 : entry.getValue().getAsJsonObject().entrySet()){
childMap.put(Integer.parseInt(entry1.getKey()), entry1.getValue().toString());
}
randomList.add(childMap);
}
return randomList;
}
}
use it by
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<ArrayList<Map<Integer, String>>>() {}.getType(), new CityListDeserializer());
Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
List<Map<Integer, String>> randomList = gson.fromJson(String.valueOf(object), new TypeToken<ArrayList<Map<Integer, String>>>() {}.getType());
you can use it by
randomList.get(index).get(39);
If you want the it Map<Map<Integer, String>>, that can also be done. Will update that also. But I would't recomment that for very large data set. HashMaps will consume a considerable amount of memory
EDIT:
you can do it this way also
public class CityListDeserializer implements JsonDeserializer<Map<String, Map<Integer, String>>>{
#Override
public Map<String, Map<Integer, String>> deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String, Map<Integer, String>> randomList = new HashMap<>();
JsonObject parentJsonObject = element.getAsJsonObject();
Map<Integer, String> childMap;
for(Map.Entry<String, JsonElement> entry : parentJsonObject.entrySet()){
childMap = new HashMap<>();
for(Map.Entry<String, JsonElement> entry1 : entry.getValue().getAsJsonObject().entrySet()){
childMap.put(Integer.parseInt(entry1.getKey()), entry1.getValue().toString());
}
randomList.put(entry.getKey(), childMap);
}
return randomList;
}
}
use it
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<Map<String, Map<Integer, String>>>() {}.getType(), new CityListDeserializer());
Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
Map<String, Map<Integer, String>> randomList = gson.fromJson(String.valueOf(object), new TypeToken<Map<String, Map<Integer, String>>>() {}.getType());
access the value by
randomList.get("commodities").get(39);
this will return you GOLD
All this was for normal json parsing. Not sure but I guess just giving the typetoken like I gave will make it work for Retrofit also
This is what you can do :)
First convert the response to JSONARRAY using
JSONArray jsonArray = new JSONArray("your string");
Then you can iterate or because you know the structre of the respobnse you can simply access it like :)
JSON commodityJSON = jsonArray.getJSONObject(0);
JSON currencies = jsonArray.getJSONObject(1);
Once you get the JSON objects access it using
commodityJSON.getString("39");
commodityJSON.getString("41");
EDIT
As per your comment :) You can do something like this i believe :)
for (int i = 0; i < jsonArray.length(); ++i) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
Iterator<String> objectKeys = jsonObject.keys();
for( String s : yourKeys){
System.out.println(jsonObject.getString(s));
}
}
Will it help buddy :) Happy coding buddy :)
I'm using a Jackson library to parse JSON:
{
"employees": [
{ "firstName":"John" , "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName":"Jones" }
]
}
Here is what I'm doing:
public void testJackson() throws IOException {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
File from = new File("emp.txt"); // JSON object comes from
TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};
HashMap<String, Object> o = mapper.readValue(from, typeRef);
Employees employees = new Employees();
employees.employees = (List<Employer>)o.get("employees"); // retrieving list of Employer(s)
employees.showEmployer(1); // choose second to print out to console
System.out.println("Got " + o); // just result of file reading
}
public static class Employees {
public List<Employer> employees;
public void showEmployer(int i) {
System.out.println(employees.get(i));
}
}
public static class Employer {
public String firstName;
public String lastName;
}
The output I'm getting:
{firstName=Anna, lastName=Smith}
Got {employees=[{firstName=John,
lastName=Doe}, {firstName=Anna, lastName=Smith}, {firstName=Peter,
lastName=Jones}]}
But I'm not expecting the elements in my List to be HashMap instances, but Employer objects. This is what Jackson library is supposed to be, isn't it? Could you guys correct me where I am wrong?
I haven't used Jackson, but it seems you're getting what you asked for - a HashMap of String, Object pairs. Perhaps you need to be more explicit in the 'value' portion of the map? Since the value is an array of Employee objects, you might try:
TypeReference<HashMap<String, List<Employee>>> typeRef = new TypeReference<HashMap<String, List<Employee>>>() {};
HashMap<String, List<Employee>> o = mapper.readValue(from, typeRef);