I'm having some trouble using Guava's Maps.difference
Right now, using this code to compare two HashMaps from two different jsons:
//Create maps from the given jsons
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> map1 = gson.fromJson(jsonObject1, type);
Map<String, Object> map2 = gson.fromJson(jsonObject2, type);
//Flatten the maps
Map<String, Object> leftFlatMap = FlatMap.flatten(map1);
Map<String, Object> rightFlatMap = FlatMap.flatten(map2);
//Check differences between both maps
MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);
Everything works fine, and compares (almost) all the elements correctly.
Problem is when one of the elements inside the HashMap is an array of maps and the elements are the same but in a different order. Like this:
FIRST JSON:
{ "body":[
{
"primitive":"VALUE",
"jsonArray":[
{
"element":83284180
},
{
"anotherElement":20832841804
}
]
}
]
}
SECOND JSON:
{
"body":[
{
"primitive":"VALUE",
"jsonArray":[
{
"anotherElement":20832841804
},
{
"element":83284180
}
]
}
]
}
As you can see, element and anotherElement values are the same but as they appear in a different order inside the array, difference shows an error.
Is there any possibility to sort the array before? or any other solution?
Thanks in advance!!
One of possible solutions may be sorting the inner sub array so that it would affect the deserialized maps (however, I think making maps out of JSON objects in this case might be a not very good idea due to deserialization costs and strategies that do not necessarily represent the original JSON object).
Assuming jsonObject1 and jsonObject2 are JsonElement implementations, you can sort its descendants.
#UtilityClass
public final class JsonElements {
public static List<JsonElement> asListView(final JsonArray jsonArray) {
return new JsonArrayListView(jsonArray);
}
public static void sort(final JsonArray jsonArray, final Comparator<? super JsonElement> comparator) {
Collections.sort(asListView(jsonArray), comparator);
}
#AllArgsConstructor(access = AccessLevel.PRIVATE)
private static final class JsonArrayListView
extends AbstractList<JsonElement> {
private final JsonArray jsonArray;
#Override
public JsonElement get(final int index) {
return jsonArray.get(index);
}
#Override
public int size() {
return jsonArray.size();
}
#Override
#SuppressWarnings("MethodDoesntCallSuperMethod")
public JsonElement set(final int index, final JsonElement element) {
return jsonArray.set(index, element);
}
}
}
public final class JsonElementsTest {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.create();
private static final Type stringToObjectMapType = new TypeToken<Map<String, Object>>() {}.getType();
#Test
public void testSort()
throws IOException {
final JsonElement jsonElement1 = ... read the 1st JSON document ...;
final JsonElement jsonElement2 = ... read the 2nd JSON document ...;
final JsonArray jsonSubArray1 = getSubArray(jsonElement1);
final JsonArray jsonSubArray2 = getSubArray(jsonElement2);
Assertions.assertNotEquals(jsonSubArray1, jsonSubArray2);
JsonElements.sort(jsonSubArray1, JsonElementsTest::compare);
JsonElements.sort(jsonSubArray2, JsonElementsTest::compare);
final Map<String, Object> map1 = gson.fromJson(jsonElement1, stringToObjectMapType);
final Map<String, Object> map2 = gson.fromJson(jsonElement2, stringToObjectMapType);
Assertions.assertEquals(map1, map2);
}
private static JsonArray getSubArray(final JsonElement jsonElement) {
return jsonElement.getAsJsonObject()
.get("body")
.getAsJsonArray()
.get(0)
.getAsJsonObject()
.get("jsonArray")
.getAsJsonArray();
}
private static int compare(final JsonElement jsonElement1, final JsonElement jsonElement2)
throws IllegalArgumentException {
final JsonObject jsonObject1 = jsonElement1.getAsJsonObject();
final int size1 = jsonObject1.size();
if ( size1 != 1 ) {
throw new IllegalArgumentException("Size-1 must equal 1, but was " + size1);
}
final JsonObject jsonObject2 = jsonElement2.getAsJsonObject();
final int size2 = jsonObject2.size();
if ( size2 != 1 ) {
throw new IllegalArgumentException("Size-2 must equal 2, but was " + size2);
}
// TODO optimize somehow
final String key1 = jsonObject1.keySet().iterator().next();
final String key2 = jsonObject2.keySet().iterator().next();
return key1.compareTo(key2);
}
}
Also consider sorting the descendants recursively if necessary.
Note that you also might have a mapping for the given JSON documents, but I don't think it's your case, but if it is, then you might want to apply #JsonAdapter to apply a special ordering deserializer (however I still don't think it's a good idea too). Or else, it is also possible to create a map view for the given JsonObjects so that it might produce recursive reordering views.
Related
I'm trying to process a json file using gson, but I'm running into a weird error. The json I'm reading from (and can't modify) has a weird way of dealing with null fields. It puts an [] in places where there is no data, causing gson to think it's an array when it's expecting a object.
An example from the gson:
//non-empty field
"prizes":[
{
"year":"1902",
"category":"physics",
"share":"2",
"motivation":"\"in recognition of the extraordinary service they rendered by their researches into the influence of magnetism upon radiation phenomena\"",
"affiliations":[
{
"name":"Leiden University",
"city":"Leiden",
"country":"the Netherlands"
}
]
}
]
//empty field
"prizes":[
{
"year":"1903",
"category":"physics",
"share":"4",
"motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
"affiliations":[
[]
]
}
]
And this is my code for processing the json:
public static void main(String[] args) throws IOException {
// Get Gson object
Gson gson = new Gson();
// read JSON file data as String
String fileData = new
String(Files.readAllBytes(Paths.get("laureates.json")));
// parse json string to object
Example laur = gson.fromJson(fileData, Example.class);
// print object data
System.out.println("\n\nLaureates Object\n\n" + laur);
}
And I have all my classes set up, i believe it will work once this issue is resolved.
The error I'm getting is "Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 3401" (column 3401 is the exact location of the first [])
The correct way to set the empty object is without the brackets. You know that. :-)
"prizes":[
{
"year":"1903",
"category":"physics",
"share":"4",
"motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
"affiliations":[
]
}
]
You maybe make a workaround removing the brackets.
fileData = fileData.replaceAll("\\[]", "");
I hope this helps.
Looks like gson is expecting an object but array is returned
Try changing Example to an array as follows.
Example[] emps= gson.fromJson(yourJson, Example
[].class);
Also see related GSON throwing "Expected BEGIN_OBJECT but was BEGIN_ARRAY"?
You can always use a type adapter to adapt bad-designed but well-formed JSON documents. For example, the following type adapter fixes your case:
final class EmptyListFixTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new EmptyListFixTypeAdapterFactory();
private EmptyListFixTypeAdapterFactory() {
}
static TypeAdapterFactory get() {
return instance;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// If it's not a list, then just let Gson pass through the rest of the type adapters chain
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// Get the original List adapter - we'll use it below
#SuppressWarnings("unchecked")
final TypeAdapter<List<Object>> delegateTypeAdapter = (TypeAdapter<List<Object>>) gson.getDelegateAdapter(this, typeToken);
// Wrap it
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) EmptyListFixTypeAdapter.get(delegateTypeAdapter);
return typeAdapter;
}
private static final class EmptyListFixTypeAdapter<E>
extends TypeAdapter<List<E>> {
// JsonParser as of Gson 2.8.2 holds no state
private static final JsonParser jsonParser = new JsonParser();
private final TypeAdapter<List<E>> delegateTypeAdapter;
private EmptyListFixTypeAdapter(final TypeAdapter<List<E>> delegateTypeAdapter) {
this.delegateTypeAdapter = delegateTypeAdapter;
}
private static <E> TypeAdapter<List<E>> get(final TypeAdapter<List<E>> delegateTypeAdapter) {
return new EmptyListFixTypeAdapter<>(delegateTypeAdapter)
.nullSafe(); // A convenient method to add null-checking automatically
}
#Override
public void write(final JsonWriter out, final List<E> value)
throws IOException {
// In case if you need to produce document with this quirks
if ( value.isEmpty() ) {
out.beginArray();
out.beginArray();
out.endArray();
out.endArray();
return;
}
delegateTypeAdapter.write(out, value);
}
#Override
public List<E> read(final JsonReader in) {
final JsonElement jsonElement = jsonParser.parse(in);
final JsonArray array = jsonElement.getAsJsonArray();
// Is it [[]]?
if ( array.size() == 1 ) {
final JsonElement element = array.get(0);
if ( element.isJsonArray() && ((JsonArray) element).size() == 0 ) {
// Yes, detected
return new ArrayList<>();
}
}
// No, proceed with the delegate type adapter
return delegateTypeAdapter.fromJsonTree(array);
}
}
}
Now suppose you have the following mappings:
final class Laureate {
final List<Prize> prizes = new ArrayList<>();
}
final class Prize {
final int year = Integer.valueOf(0);
final String category = null;
final List<Affiliation> affiliations = new ArrayList<>();
}
final class Affiliation {
final String name = null;
final String city = null;
final String country = null;
}
And then:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(EmptyListFixTypeAdapterFactory.get())
.create();
private static final Type laureatesType = new TypeToken<List<Laureate>>() {
}.getType();
public static void main(final String... args)
throws IOException {
try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q49603826.class, "laureates.json") ) {
gson.<List<Laureate>>fromJson(jsonReader, laureatesType)
.stream()
.flatMap(laureate -> laureate.prizes.stream())
.peek(prize -> System.out.println("Prize: " + prize.year + " " + prize.category))
.flatMap(prize -> prize.affiliations.stream())
.peek(affiliation -> System.out.println("\tAffiliation: " + affiliation.name + " " + affiliation.city + " " + affiliation.country))
.forEach(affiliation -> {
});
}
}
Output:
Prize: 1902 physics
........Affiliation: Leiden University Leiden the Netherlands
Prize: 1903 physics
I have a JSON that I convert into POJOs. The JSON is read from a GZIPInputStream gis.
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<Long, ConfigMasterAirportData>> typeRef =
new TypeReference<Map<Long, ConfigMasterAirportData>>() {};
Map<Long, ConfigMasterAirportData> configMasterAirportMap =
mapper.readValue(gis, typeRef);
I do not want new Long objects to be created for each entry. I want it to get Long objects from a custom LongPool I have created. Is there a way to pass such a LongPool to the mapper?
If not, is there another JSON library I can use to do that?
There are many ways to achieve this if you are sure that object pooling is required in your case.
First of all, Java already does Long object pooling for a small range between -128 and 127 inclusive. See source code of Long.valueOf.
Let us have 2 JSON objects that we want to deserialize: map1 and map2:
final String map1 = "{\"1\": \"Hello\", \"10000000\": \"world!\"}";
final String map2 = "{\"1\": \"You\", \"10000000\": \"rock!\"}";
Standard deserialization
If we use standard deserialization:
final ObjectMapper mapper = new ObjectMapper();
final TypeReference<Map<Long, String>> typeRef = new TypeReference<Map<Long, String>>() {};
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, typeRef);
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, typeRef);
printMap(deserializedMap1);
printMap(deserializedMap2);
Where printMap is defined as
private static void printMap(Map<Long, String> longStringMap) {
longStringMap.forEach((Long k, String v) -> {
System.out.printf("key object id %d \t %s -> %s %n", System.identityHashCode(k), k, v);
});
}
we get the following output:
key object id 1635756693 1 -> Hello
key object id 504527234 10000000 -> world!
key object id 1635756693 1 -> You
key object id 101478235 10000000 -> rock!
Note that key 1 is the same object with hashcode 1635756693 in both maps. This is due to built-in pool for [-128,127] range.
Solution1: #JsonAnySetter deserialization
We can define a wrapper object for the map and use #JsonAnySetter annotation to intercept all key-value pairs being deserialized. Then we can intern each Long object using Guava StrongInterner:
static class CustomLongPoolingMap {
private static final Interner<Long> LONG_POOL = Interners.newStrongInterner();
private final Map<Long, String> map = new HashMap<>();
#JsonAnySetter
public void addEntry(String key, String value) {
map.put(LONG_POOL.intern(Long.parseLong(key)), value);
}
public Map<Long, String> getMap() {
return map;
}
}
We will use it like this:
final ObjectMapper mapper = new ObjectMapper();
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, CustomLongPoolingMap.class).getMap();
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, CustomLongPoolingMap.class).getMap();
Output:
key object id 1635756693 1 -> Hello
key object id 1596467899 10000000 -> world!
key object id 1635756693 1 -> You
key object id 1596467899 10000000 -> rock!
Now you can see that key 10000000 is also the same object in both maps with hashcode 1596467899
Solution 2: Register custom KeyDeserializer
Define custom KeySerializer:
public static class MyCustomKeyDeserializer extends KeyDeserializer {
private static final Interner<Long> LONG_POOL = Interners.newStrongInterner();
#Override
public Long deserializeKey(String key, DeserializationContext ctxt) {
return LONG_POOL.intern(Long.parseLong(key));
}
}
And register it with the ObjectMapper:
final SimpleModule module = new SimpleModule();
module.addKeyDeserializer(Long.class, new MyCustomKeyDeserializer());
final ObjectMapper mapper = new ObjectMapper().registerModule(module);
final TypeReference<Map<Long, String>> typeRef = new TypeReference<Map<Long, String>>() {};
final Map<Long, String> deserializedMap1 = mapper.readValue(map1, typeRef);
final Map<Long, String> deserializedMap2 = mapper.readValue(map2, typeRef);
Solution 3: Use custom KeyDeserializer via #JsonDeserialize annotation
Define a wrapper object
static class MapWrapper {
#JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map1;
#JsonDeserialize(keyUsing = MyCustomKeyDeserializer.class)
private Map<Long, String> map2;
}
And deserialize it:
final ObjectMapper mapper = new ObjectMapper();
final String json = "{\"map1\": " + map1 + ", \"map2\": " + map2 + "}";
final MapWrapper wrapper = mapper.readValue(json, MapWrapper.class);
final Map<Long, String> deserializedMap1 = wrapper.map1;
final Map<Long, String> deserializedMap2 = wrapper.map2;
Solution 4: Use Trove library TLongObjectMap to avoid using Long objects entirely
Trove library implements maps that use primitive types for keys to remove overhead of boxed objects entirely. It's in somewhat dormant state however.
You need in your case TLongObjectHashMap.
There is a library that defines a deserializer for TIntObjectMap:
https://bitbucket.org/marshallpierce/jackson-datatype-trove/src/d7386afab0eece6f34a0af69b76b478f417f0bd4/src/main/java/com/palominolabs/jackson/datatype/trove/deser/TIntObjectMapDeserializer.java?at=master&fileviewer=file-view-default
I think it will be quite easy to adapt it for TLongObjectMap.
Full code for this answer can be found here: https://gist.github.com/shtratos/f0a81515d19b858dafb71e86b62cb474
I've used answers to this question for solutions 2 & 3:
Deserializing non-string map keys with Jackson
Not sure about Jackson library, but with Google Gson you can quite simply do it by registering a custom type adapter whose responsibility is to resolve every key the way you want it:
public class DeserializeJsonMapWithCustomKeyResolver {
public static void main(String[] args) {
final String JSON = "{ \"1\" : { \"value\" :1 }, \"2\" : { \"value\" : 2} }";
final Type mapType = new TypeToken<Map<Long, ConfigMasterAirportData>>() {}.getType();
final Map<String, ConfigMasterAirportData> map =
new GsonBuilder().registerTypeAdapter(mapToken, new PooledLongKeyDeserializer())
.create()
.fromJson(JSON, mapType);
System.out.println(map);
}
static Long longFromString(String value)
{
System.out.println("Resolving value : " + value);
// TODO: replace with your LongPool call here instead; may need to convert from String
return Long.valueOf(value);
}
static class PooledLongKeyDeserializer implements
JsonDeserializer<Map<Long, ConfigMasterAirportData>>
{
#Override
public Map<Long, ConfigMasterAirportData> deserialize(
JsonElement json,
Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException
{
final Map<Long, ConfigMasterAirportData> map = json.getAsJsonObject()
.entrySet()
.stream()
.collect(
Collectors.toMap(
e -> longFromString(e.getKey()),
e -> context.deserialize(e.getValue(),
TypeToken.get(ConfigMasterAirportData.class).getType())
));
return map;
}
}
static class ConfigMasterAirportData {
public int value;
#Override
public String toString() { return "ConfigMasterAirportData{value=" + value + '}'; }
}
}
I have a JSON string like this:
{
"r": [
{
"pic": "1.jpg",
"name": "Name1"
},
{
"pic": "2.jpg",
"name": "Name2"
},
{
"pic": "3.jpg",
"name": "Name3"
}
]
}
I want to parse to this POJO model:
public class Catalog {
#SerializedName("r")
#Expose
private List<JSONObject> r = new ArrayList<JSONObject>();
public List<JSONObject> getR() {
return r;
}
public void setR(List<JSONObject> r) {
this.r = r;
}
}
I am parsing this way:
Catalog cat = new Gson().fromJson(jsonString,Catalog.class);
But finally am getting this json
{
"r": [
{
"nameValuePairs": {}
},
{
"nameValuePairs": {}
},
{
"nameValuePairs": {}
}
]
}
Please note that I don't want to use com.google.gson.JsonObject.
I want to use org.json.JSONObject. How to achieve this because almost all of my code uses it?
As it was already mentioned in other answer and comments, you probably might not really want to use org.json.JSONObject for several reasons. But if it's a must for you, you just have to create your org.json.JSONObject-aware Gson instance.
final class JSONObjectJsonDeserializer
implements JsonDeserializer<JSONObject> {
// The implementation is fully thread-safe and can be instantiated once
private static final JsonDeserializer<JSONObject> jsonObjectJsonDeserializer = new JSONObjectJsonDeserializer();
// Type tokens are immutable values and therefore can be considered constants (and final) and thread-safe as well
private static final TypeToken<Map<String, Object>> mapStringToObjectTypeToken = new TypeToken<Map<String, Object>>() {
};
private JSONObjectJsonDeserializer() {
}
static JsonDeserializer<JSONObject> getJsonObjectJsonDeserializer() {
return jsonObjectJsonDeserializer;
}
#Override
public JSONObject deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
// Convert the input jsonElement as if it were a Map<String, Object> (a generic representation for JSON objectS)
final Map<String, Object> map = context.deserialize(jsonElement, mapStringToObjectTypeToken.getType());
// And forward the map to the JSONObject constructor - it seems to accept it nice
return new JSONObject(map);
}
}
Gson is designed thread-safe and does not need to be instantiated every time serialization or deserialization is necessary:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(JSONObject.class, getJsonObjectJsonDeserializer())
.create();
And finally:
final Catalog catalog = gson.fromJson(jsonString, Catalog.class);
out.println(catalog.getR());
with the following result:
[{"name":"Name1","pic":"1.jpg"}, {"name":"Name2","pic":"2.jpg"}, {"name":"Name3","pic":"3.jpg"}]
Anyway, I would suggest you to redesign your mappings model.
I think you don't need JSONObject.
Try this
// is wrapped class for serialized json.
public class JsonExample
{
List<Catalog> r;
}
public class Catalog {
private String pic;
private String name;
public String getPic() {
return pic;
}
public String getName() {
return name;
}
}
JsonExample example = new Gson().fromJson(json, JsonExample.class);
Additional - using JSONObject
JSONObject obj = new JSONObject(json);
JSONArray arr = obj.getJSONArray("r");
List<Catalog> cataList = new ArrayList<>();
for(int i = 0 ; i < arr.length() ; ++i)
{
cataList.add(new Catalog(arr.getJSONObject(i)));
}
public class Catalog {
private String pic;
private String name;
public Catalog(JSONObject obj) throws JSONException
{
pic = obj.getString("pic");
name = obj.getString("name");
}
public String getPic() {
return pic;
}
public String getName() {
return name;
}
}
I think in your case, usage of gson library is not required at all.
Only org.json can solve the entire problem.
E.g.:
JSONObject json = new JSONObject(jsonString);
JSONArray jsonArray = json.getJSONArray("r");
List<JSONObject> jsonList = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
jsonList.add(jsonArray.getJSONObject(i));
}
Catalog catalog = new Catalog();
catalog.setR(jsonList);
I ran into a little snag with the concrete implementation of strategy components using generic types.
Wondering if anyone can point me in the right direction with an example?
Here is what I am working towards, but I get caught up when I declare the decode method as it expcects a List when I create the ArrayList... Not a surprise.
public class CsvFormat<T,T1> implements FormatStrategy<T,T1> {
public CsvFormat(boolean header) {
setHeader(header);
#Override
public final T decode(T1 csvData) {
csvData = new ArrayList(); //ERROR****
List<Map<String, String>> decodedData = new ArrayList<Map<String, String>>(); //turn collection into an array of maps
if (this.hasHeader()) {
decodeDataWithHeader(csvData, decodedData);
} else {
decodeDataNoHeader(csvData, decodedData);
}
return decodedData;
}
private void decodeDataNoHeader(List<String> csvData, List<Map<String, String>> records) {
int recordCount = FIRST_IDX;
List<String> fields = null; //= Arrays.asList(csvData.get(recordCount).split(DELIM)); //turn line into a list, first record
for (String data : csvData) { //for each unformatted string
int delimIndex = FIRST_IDX; //reset delim
fields = Arrays.asList(data.split(DELIM));//after header, start mapping
records.add(new LinkedHashMap<String, String>()); //make a new map
recordCount++;
for (String field : fields) {
final String KEY_ID = "Column-" + (delimIndex + RECORD_BUFFER);
records.get(records.size() - RECORD_BUFFER).put(KEY_ID, field);
delimIndex++;
}
}
}
Here is what I had to start with The only way I can think of so far to achieve the above without error is to overload the the decode methods based on what object they are passed..
public class CsvFormat implements FormatStrategy<
List<Map<String, String>>, List<String>> {
public CsvFormat(boolean header) {
setHeader(header);
}
#Override
public final List<Map<String, String>> decode(List<String> csvData) {
List<Map<String, String>> decodedData = new ArrayList<Map<String, String>>(); //turn collection into an array of maps
if (this.hasHeader()) {
decodeDataWithHeader(csvData, decodedData);
} else {
decodeDataNoHeader(csvData, decodedData);
}
return decodedData;
}
private void decodeDataNoHeader(List<String> csvData, List<Map<String, String>> records) {
int recordCount = FIRST_IDX;
List<String> fields = null; //= Arrays.asList(csvData.get(recordCount).split(DELIM)); //turn line into a list, first record
for (String data : csvData) { //for each unformatted string
int delimIndex = FIRST_IDX; //reset delim
fields = Arrays.asList(data.split(DELIM));//after header, start mapping
records.add(new LinkedHashMap<String, String>()); //make a new map
recordCount++;
for (String field : fields) {
final String KEY_ID = "Column-" + (delimIndex + RECORD_BUFFER);
records.get(records.size() - RECORD_BUFFER).put(KEY_ID, field);
delimIndex++;
}
}
}
Actually the example you "started with" seems exactly right. You have written a decode method that requires a List<String> as input, so it stands to reason that you would be implementing the FormatStrategy interface with that specific type as T1 and the same goes for the output type T.
Why would you do icky runtime inspection of the input and loads of unsafe casting when you can actually follow the pattern and create a new class for each specific concrete type you care about?
The code you have written for decoding the data will always return a List<Map<String, String>> and can only work with a List<String> as input, so there is no reason for the CsvFormat class to have type parameters. So what you started with seems correct, why aren't you satisfied with it?
I have a HashMap in Java, the contents of which (as you all probably know) can be accessed by
HashMap.get("keyname");
If a have a HashMap inside another HashMap i.e. a nested HashMap, how would i access the contents? Can i do this like this, inline:
HashMap.get("keyname").get("nestedkeyname");
Thank you.
You can do it like you assumed. But your HashMap has to be templated:
Map<String, Map<String, String>> map =
new HashMap<String, Map<String, String>>();
Otherwise you have to do a cast to Map after you retrieve the second map from the first.
Map map = new HashMap();
((Map)map.get( "keyname" )).get( "nestedkeyname" );
You can get the nested value by repeating .get(), but with deeply nested maps you have to do a lot of casting into Map. An easier way is to use a generic method for getting a nested value.
Implementation
public static <T> T getNestedValue(Map map, String... keys) {
Object value = map;
for (String key : keys) {
value = ((Map) value).get(key);
}
return (T) value;
}
Usage
// Map contents with string and even a list:
{
"data": {
"vehicles": {
"list": [
{
"registration": {
"owner": {
"id": "3643619"
}
}
}
]
}
}
}
List<Map> list = getNestedValue(mapContents, "data", "vehicles", "list");
Map first = list.get(0);
String id = getNestedValue(first, "registration", "owner", "id");
Yes.
See:
public static void main(String args[]) {
HashMap<String, HashMap<String, Object>> map = new HashMap<String, HashMap<String,Object>>();
map.put("key", new HashMap<String, Object>());
map.get("key").put("key2", "val2");
System.out.println(map.get("key").get("key2"));
}
If you plan on constructing HashMaps with variable depth, use a recursive data structure.
Below is an implementation providing a sample interface:
class NestedMap<K, V> {
private final HashMap<K, NestedMap> child;
private V value;
public NestedMap() {
child = new HashMap<>();
value = null;
}
public boolean hasChild(K k) {
return this.child.containsKey(k);
}
public NestedMap<K, V> getChild(K k) {
return this.child.get(k);
}
public void makeChild(K k) {
this.child.put(k, new NestedMap());
}
public V getValue() {
return value;
}
public void setValue(V v) {
value = v;
}
}
and example usage:
class NestedMapIllustration {
public static void main(String[] args) {
NestedMap<Character, String> m = new NestedMap<>();
m.makeChild('f');
m.getChild('f').makeChild('o');
m.getChild('f').getChild('o').makeChild('o');
m.getChild('f').getChild('o').getChild('o').setValue("bar");
System.out.println(
"nested element at 'f' -> 'o' -> 'o' is " +
m.getChild('f').getChild('o').getChild('o').getValue());
}
}
As others have said you can do this but you should define the map with generics like so:
Map<String, Map<String, String>> map = new HashMap<String, Map<String,String>>();
However, if you just blindly run the following:
map.get("keyname").get("nestedkeyname");
you will get a null pointer exception whenever keyname is not in the map and your program will crash. You really should add the following check:
String valueFromMap = null;
if(map.containsKey("keyname")){
valueFromMap = map.get("keyname").get("nestedkeyname");
}
Yes, if you use the proper generic type signature for the outer hashmap.
HashMap<String, HashMap<String, Foo>> hm = new HashMap<String, HashMap<String, Foobar>>();
// populate the map
hm.get("keyname").get("nestedkeyname");
If you're not using generics, you'd have to do a cast to convert the object retrieved from the outer hash map to a HashMap (or at least a Map) before you could call its get() method. But you should be using generics ;-)
I prefer creating a custom map that extends HashMap. Then just override get() to add extra logic so that if the map doesnt contain your key. It will a create a new instance of the nested map, add it, then return it.
public class KMap<K, V> extends HashMap<K, V> {
public KMap() {
super();
}
#Override
public V get(Object key) {
if (this.containsKey(key)) {
return super.get(key);
} else {
Map<K, V> value = new KMap<K, V>();
super.put((K)key, (V)value);
return (V)value;
}
}
}
Now you can use it like so:
Map<Integer, Map<Integer, Map<String, Object>>> nestedMap = new KMap<Integer, Map<Integer, Map<String, Object>>>();
Map<String, Object> map = (Map<String, Object>) nestedMap.get(1).get(2);
Object obj= new Object();
map.put(someKey, obj);
I came to this StackOverflow page looking for a something ala valueForKeyPath known from objc. I also came by another post - "Key-Value Coding" for Java, but ended up writing my own.
I'm still looking for at better solution than PropertyUtils.getProperty in apache's beanutils library.
Usage
Map<String, Object> json = ...
public String getOptionalFirstName() {
return MyCode.getString(json, "contact", "firstName");
}
Implementation
public static String getString(Object object, String key0, String key1) {
if (key0 == null) {
return null;
}
if (key1 == null) {
return null;
}
if (object instanceof Map == false) {
return null;
}
#SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>)object;
Object object1 = map.get(key0);
if (object1 instanceof Map == false) {
return null;
}
#SuppressWarnings("unchecked")
Map<Object, Object> map1 = (Map<Object, Object>)object1;
Object valueObject = map1.get(key1);
if (valueObject instanceof String == false) {
return null;
}
return (String)valueObject;
}
import java.util.*;
public class MyFirstJava {
public static void main(String[] args)
{
Animal dog = new Animal();
dog.Info("Dog","Breezi","Lab","Chicken liver");
dog.Getname();
Animal dog2= new Animal();
dog2.Info("Dog", "pumpkin", "POM", "Pedigree");
dog2.Getname();
HashMap<String, HashMap<String, Object>> dogs = new HashMap<>();
dogs.put("dog1", new HashMap<>() {{put("Name",dog.name);
put("Food",dog.food);put("Age",3);}});
dogs.put("dog2", new HashMap<>() {{put("Name",dog2.name);
put("Food",dog2.food);put("Age",6);}});
//dogs.get("dog1");
System.out.print(dogs + "\n");
System.out.print(dogs.get("dog1").get("Age"));
}
}
Example Map:
{
"data": {
"userData": {
"location": {
"city": "Banja Luka"
}
}
}
}
Implementation:
public static Object getValueFromMap(final Map<String, Object> map, final String key) {
try {
final String[] tmpKeys = key.split("\\.");
Map<String, Object> currentMap = map;
for (int i = 0; i < tmpKeys.length - 1; i++) {
currentMap = (Map<String, Object>) currentMap.get(tmpKeys[i]);
}
return currentMap.get(tmpKeys[tmpKeys.length - 1]);
} catch (Exception exception) {
return null;
}
}
Usage:
final Map<String, Object> data = new HashMap<>();
final Map<String, Object> userData = new HashMap<>();
final Map<String, Object> location = new HashMap<>();
location.put("city", "Banja Luka");
userData.put("location", location);
data.put("userData", userData);
System.out.println(getValueFromMap(data, "userData.location.city"));
Result:
Banja Luka
Process finished with exit code 0
I hit this discussion while trying to figure out how to get a value from a nested map of unknown depth and it helped me come up with the following solution to my problem. It is overkill for the original question but maybe it will be helpful to someone that finds themselves in a situation where you have less knowledge about the map being searched.
private static Object pullNestedVal(
Map<Object, Object> vmap,
Object ... keys) {
if ((keys.length == 0) || (vmap.size() == 0)) {
return null;
} else if (keys.length == 1) {
return vmap.get(keys[0]);
}
Object stageObj = vmap.get(keys[0]);
if (stageObj instanceof Map) {
Map<Object, Object> smap = (Map<Object, Object>) stageObj;
Object[] skeys = Arrays.copyOfRange(keys, 1, keys.length);
return pullNestedVal(smap, skeys);
} else {
return null;
}
}