Hashmap:
public HashMap<Perk, Boolean> perks;
public HashMap<Perk, Boolean> getPerks() {
return perks;
}
How I save (works fine, I'm using GSON by Google):
object.add("perks", builder.toJsonTree(player.perks));
Preview of save:
"perks": {
"DAMAGE_AMPLIFIER": true
}
How I am attempting to load but isn't working, error is below.
if (reader.has("perks")) {
jsonToMap(reader.get("perks").toString(), player);
}
jsonToMap method:
public static void jsonToMap(String t, Player player) throws JSONException {
JSONObject jObject = new JSONObject(t);
Iterator<?> keys = jObject.keys();
while(keys.hasNext()) {
String key = keys.next().toString();
boolean value = jObject.getBoolean(key);
player.perks.put(Perk.valueOf(key), value);
}
}
Error:
java.lang.NullPointerException
at com.callisto.world.entity.impl.player.PlayerLoading.jsonToMap(PlayerLoading.java:730)
at com.callisto.world.entity.impl.player.PlayerLoading.getResult(PlayerLoading.java:684)
If I have to guess perks is null and that's why when you do put you get a null pointer. Change it to:
public HashMap<Perk, Boolean> perks=new HashMap<>(); //We init the hashmap here so we can putt stuff after that
public HashMap<Perk, Boolean> getPerks() {
return perks;
}
Also you might even change it to :
Map<Perk, Boolean> perks=new HashMap<>(); and use the interface and not the implementation in your methods
Related
I've recently been faced with a tough json (that I don't control, so I have to deal with it):
{
"someOtherParam":"someValue",
"attributes":
{
"language":["fr", "en"],
"otherParam":["value1", "value2"]
}
}
attributes is a map - I don't know what attributes it may contain, so I can't just map it to an object. In Java I believe I need to map is as a Map<String,List<String>> somehow.
I've found a very helpful post that allowed me to write an adapter like this:
public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, List<String>>> {
public static class AdaptedMap {
#XmlVariableNode("key")
List<AdaptedEntry> entries = new ArrayList<>();
}
public static class AdaptedEntry {
#XmlTransient
public String key;
#XmlValue
public List<String> value;
}
#Override
public AdaptedMap marshal(Map<String, List<String>> map) throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for(Map.Entry<String, List<String>> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<String, List<String>> unmarshal(AdaptedMap adaptedMap) throws Exception {
List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
Map<String, List<String>> map = new HashMap<>(adaptedEntries.size());
for(AdaptedEntry adaptedEntry : adaptedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
return map;
}
}
And while this general approach would work for simple values (so a Map<String,String> for instance), here on marshalling it insists on mapping the list as a simple element
{
"someOtherParam":"someValue",
"attributes":
{
"language":"fr en",
"otherParam":"value1 value2"
}
}
So how do I do this correctly?
I am getting json from dynamoDb that looks like this -
{
"id": "1234",
"payment": {
"payment_id": "2345",
"user_defined": {
"some_id": "3456"
}
}
}
My aim is to get the user_defined field in a Java HashMap<String, Object> as user_defined field can contain any user defined fields, which would be unknown until the data arrives. Everything works fine except my DynamoDBMapper cannot convert the user_defined field to a Java HashMap. It is throwing this error -
Exception occured Response[payment]; could not unconvert attribute
This is how the classes looks like -
#DynamoDBTable(tableName = "PaymentDetails")
public class Response {
private String id;
public Response() {
}
private Payment payment = new Payment();
#DynamoDBHashKey(attributeName="id")
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public Payment getPayment() {
return payment;
}
public void setPayment(Payment payment) {
this.payment = payment;
}
}
The payment field mapper -
#DynamoDBDocument
public class Payment {
private String payment_id:
private HashMap<String, Object> user_defined;
public Payment() {}
public getPayment_id() {
return payment_id;
}
public setPayment_id(String payment_id) {
this.payment_id = payment_id;
}
#DynamoDBTypeConverted(converter = HashMapMarshaller.class)
public HashMap<String, Object> getUser_defined() {
return user_defined;
}
public void setUser_defined(HashMap<String, Object> user_defined) {
this.user_defined = user_defined;
}
}
The HashMapMarshaller(Just to check if Hashmap marshaller wasn't working with gson, I just defined a Hashmap, put in a value and return it, but seems to still not working) -
public class HashMapMarshaller implements DynamoDBTypeConverter<String, HashMap<String, Object>> {
#Override
public String convert(HashMap<String, Object> hashMap) {
return new Gson().toJson(hashMap);
}
#Override
public HashMap<String, Object> unconvert(String jsonString) {
System.out.println("jsonString received for unconverting is " + jsonString);
System.out.println("Unconverting attribute");
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
return hashMap;
//return new Gson().fromJson(jsonString, new TypeToken<HashMap<String, Object>>(){}.getType());
}
}
Marshaller approach is till now not working for me. It is also not printing any of the printlns I've put in there. I've also tried using #DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.M) and using Map instead of HashMap above my user_defined getter to no avail.
I want to find out how to convert the user_defined field to Java HashMap or Map. Any help is appreciated. Thank you!
Make Map<String, Object> to Map<String, String>. It should work without any custom converters. Otherwise be specific about Map's value type. For example, Map<String, SimplePojo> should work. Don't forget to annotate SimplePojo class with #DynamoDBDocument.
With Object as a type of Map's value, DynamoDB will not able to decide which object it has to create while reading entry from DynamoDB. It should know about specific type like String, Integer, SimplePojo etc.
I'm wondering, if there is a generic way to fill a map with properties you just know the prefix.
Assuming there are a bunch of properties like
namespace.prop1=value1
namespace.prop2=value2
namespace.iDontKnowThisNameAtCompileTime=anothervalue
I'd like to have a generic way to fill this property inside a map, something like
#Component
#ConfigurationProperties("namespace")
public class MyGenericProps {
private Map<String, String> propmap = new HashMap<String, String>();
// setter and getter for propmap omitted
public Set<String> returnAllKeys() {
return propmap.keySet();
}
}
Or is there another convenient way to collect all properties with a certain prefix, instead of iterating over all PropertySources in the environment?
Thanks
Hansjoerg
As long as you're happy having every property added into the map, rather than just those that you don't know in advance, you can do this with #ConfigurationProperties. If you want to grab everything that's beneath namespace then you need to use an empty prefix and provide a getter for a map named namespace:
#ConfigurationProperties("")
public class CustomProperties {
private final Map<String, String> namespace = new HashMap<>();
public Map<String, String> getNamespace() {
return namespace;
}
}
Spring Boot uses the getNamespace method to retrieve the map so that it can add the properties to it. With these properties:
namespace.a=alpha
namespace.b=bravo
namespace.c=charlie
The namespace map will contain three entries:
{a=alpha, b=bravo, c=charlie}
If the properties were nested more deeply, for example:
namespace.foo.bar.a=alpha
namespace.foo.bar.b=bravo
namespace.foo.bar.c=charlie
Then you'd use namespace.foo as the prefix and rename namespace and getNamespace on CustomProperties to bar and getBar respectively.
Note that you should apply #EnableConfigurationProperties to your configuration to enable support for #ConfigurationProperties. You can then reference any beans that you want to be processed using that annotation, rather than providing an #Bean method for them, or using #Component to have them discovered by component scanning:
#SpringBootApplication
#EnableConfigurationProperties(CustomProperties.class)
public class YourApplication {
// …
}
In addition to this, my problem was that I didn't had multiple simple key/value properties but whole objects:
zuul:
routes:
query1:
path: /api/apps/test1/query/**
stripPrefix: false
url: "https://test.url.com/query1"
query2:
path: /api/apps/test2/query/**
stripPrefix: false
url: "https://test.url.com/query2"
index1:
path: /api/apps/*/index/**
stripPrefix: false
url: "https://test.url.com/index"
Following Jake's advice I tried to use a Map with a Pojo like this:
#ConfigurationProperties("zuul")
public class RouteConfig {
private Map<String, Route> routes = new HashMap<>();
public Map<String, Route> getRoutes() {
return routes;
}
public static class Route {
private String path;
private boolean stripPrefix;
String url;
// [getters + setters]
}
}
Works like a charm,
Thanks!
I was going nuts trying to understand why #Andy's answer wasn't working for me (as in, the Map was remaining empty) just to realize that I had Lombok's #Builder annotation getting in the way, which added a non-empty constructor. I'm adding this answer to emphasize that in order for #ConfigurationProperties to work on Map, the value type must have a No-Arguments constructor. This is also mentioned in Spring's documentation:
Such arrangement relies on a default empty constructor and getters and setters are usually mandatory ...
I hope this will save someone else some time.
I wrote myself a MapFilter class to handle this efficiently. Essentially, you create a Map and then filter it by specifying a prefix for the key. There is also a constructor that takes a Properties for convenience.
Be aware that this just filters the main map. Any changes applied to the filtered map are also applied to the base map, including deletions etc but obviously changes to the main map will not be reflected in the filtered map until something causes a rebuild.
It is also very easy (and efficient) to filter already filtered maps.
public class MapFilter<T> implements Map<String, T> {
// The enclosed map -- could also be a MapFilter.
final private Map<String, T> map;
// Use a TreeMap for predictable iteration order.
// Store Map.Entry to reflect changes down into the underlying map.
// The Key is the shortened string. The entry.key is the full string.
final private Map<String, Map.Entry<String, T>> entries = new TreeMap<>();
// The prefix they are looking for in this map.
final private String prefix;
public MapFilter(Map<String, T> map, String prefix) {
// Store my backing map.
this.map = map;
// Record my prefix.
this.prefix = prefix;
// Build my entries.
rebuildEntries();
}
public MapFilter(Map<String, T> map) {
this(map, "");
}
private synchronized void rebuildEntries() {
// Start empty.
entries.clear();
// Build my entry set.
for (Map.Entry<String, T> e : map.entrySet()) {
String key = e.getKey();
// Retain each one that starts with the specified prefix.
if (key.startsWith(prefix)) {
// Key it on the remainder.
String k = key.substring(prefix.length());
// Entries k always contains the LAST occurrence if there are multiples.
entries.put(k, e);
}
}
}
#Override
public String toString() {
return "MapFilter (" + prefix + ") of " + map + " containing " + entrySet();
}
// Constructor from a properties file.
public MapFilter(Properties p, String prefix) {
// Properties extends HashTable<Object,Object> so it implements Map.
// I need Map<String,T> so I wrap it in a HashMap for simplicity.
// Java-8 breaks if we use diamond inference.
this(new HashMap<String, T>((Map) p), prefix);
}
// Helper to fast filter the map.
public MapFilter<T> filter(String prefix) {
// Wrap me in a new filter.
return new MapFilter<>(this, prefix);
}
// Count my entries.
#Override
public int size() {
return entries.size();
}
// Are we empty.
#Override
public boolean isEmpty() {
return entries.isEmpty();
}
// Is this key in me?
#Override
public boolean containsKey(Object key) {
return entries.containsKey(key);
}
// Is this value in me.
#Override
public boolean containsValue(Object value) {
// Walk the values.
for (Map.Entry<String, T> e : entries.values()) {
if (value.equals(e.getValue())) {
// Its there!
return true;
}
}
return false;
}
// Get the referenced value - if present.
#Override
public T get(Object key) {
return get(key, null);
}
// Get the referenced value - if present.
public T get(Object key, T dflt) {
Map.Entry<String, T> e = entries.get((String) key);
return e != null ? e.getValue() : dflt;
}
// Add to the underlying map.
#Override
public T put(String key, T value) {
T old = null;
// Do I have an entry for it already?
Map.Entry<String, T> entry = entries.get(key);
// Was it already there?
if (entry != null) {
// Yes. Just update it.
old = entry.setValue(value);
} else {
// Add it to the map.
map.put(prefix + key, value);
// Rebuild.
rebuildEntries();
}
return old;
}
// Get rid of that one.
#Override
public T remove(Object key) {
// Do I have an entry for it?
Map.Entry<String, T> entry = entries.get((String) key);
if (entry != null) {
entries.remove(key);
// Change the underlying map.
return map.remove(prefix + key);
}
return null;
}
// Add all of them.
#Override
public void putAll(Map<? extends String, ? extends T> m) {
for (Map.Entry<? extends String, ? extends T> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
// Clear everything out.
#Override
public void clear() {
// Just remove mine.
// This does not clear the underlying map - perhaps it should remove the filtered entries.
for (String key : entries.keySet()) {
map.remove(prefix + key);
}
entries.clear();
}
#Override
public Set<String> keySet() {
return entries.keySet();
}
#Override
public Collection<T> values() {
// Roll them all out into a new ArrayList.
List<T> values = new ArrayList<>();
for (Map.Entry<String, T> v : entries.values()) {
values.add(v.getValue());
}
return values;
}
#Override
public Set<Map.Entry<String, T>> entrySet() {
// Roll them all out into a new TreeSet.
Set<Map.Entry<String, T>> entrySet = new TreeSet<>();
for (Map.Entry<String, Map.Entry<String, T>> v : entries.entrySet()) {
entrySet.add(new Entry<>(v));
}
return entrySet;
}
/**
* An entry.
*
* #param <T>
*
* The type of the value.
*/
private static class Entry<T> implements Map.Entry<String, T>, Comparable<Entry<T>> {
// Note that entry in the entry is an entry in the underlying map.
private final Map.Entry<String, Map.Entry<String, T>> entry;
Entry(Map.Entry<String, Map.Entry<String, T>> entry) {
this.entry = entry;
}
#Override
public String getKey() {
return entry.getKey();
}
#Override
public T getValue() {
// Remember that the value is the entry in the underlying map.
return entry.getValue().getValue();
}
#Override
public T setValue(T newValue) {
// Remember that the value is the entry in the underlying map.
return entry.getValue().setValue(newValue);
}
#Override
public boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry e = (Entry) o;
return getKey().equals(e.getKey()) && getValue().equals(e.getValue());
}
#Override
public int hashCode() {
return getKey().hashCode() ^ getValue().hashCode();
}
#Override
public String toString() {
return getKey() + "=" + getValue();
}
#Override
public int compareTo(Entry<T> o) {
return getKey().compareTo(o.getKey());
}
}
// Simple tests.
public static void main(String[] args) {
String[] samples = {
"Some.For.Me",
"Some.For.You",
"Some.More",
"Yet.More"};
Map map = new HashMap();
for (String s : samples) {
map.put(s, s);
}
Map all = new MapFilter(map);
Map some = new MapFilter(map, "Some.");
Map someFor = new MapFilter(some, "For.");
System.out.println("All: " + all);
System.out.println("Some: " + some);
System.out.println("Some.For: " + someFor);
}
}
Most of the places I pass through are not fully descriptive or are a bit outdated, so here are the full steps that I have taken to do that. Note: The Spring boot version I used is 2.4.0:
Add in pom.xml - spring-boot-configuration-processor
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Create a properties file and set data in it - shared-messages.properties Note: "LIBRARY_10001" is the map key, "Unable to find the book" is the map value
shared-messages.messages.LIBRARY_10001=Unable to find the book
shared-messages.messages.LIBRARY_10002=Book already exists
Create a Properties class that uses the properties file
#ConfigurationProperties("shared-messages")
#Getter
public class LibraryProperties {
private final Map<String, String> messages = new HashMap<>();
}
On the Application level, define the Property source and Enable Configuration Property
#EnableConfigurationProperties(LibraryProperties.class)
#PropertySource("shared-messages.properties")
public class LibraryApplication {
....
}
On the Service level, inject "LibraryProperties" class and access the property you need from
#Autowired
private LibraryProperties libraryProperties;
libraryProperties.getMessages().get("LIBRARY_10001")
It might not be the perfect solution but I shared the way I managed to do that because I tried different combinations that didn't work for me
I have this code in my JSP page:
<h:selectManyCheckbox id="chb" value="#{MyBean.selectedCheckBoxes}" layout="pageDirection">
<f:selectItems value="#{MyBean.checkBoxItems}"/>
</h:selectManyCheckbox>
And in my MyBean:
public class MyBean {
public MyBean() {
for (Elem section : sections) {
checkBoxItems.put(section.getName(), section.getObjectID());
}
}
private String[] selectedCheckBoxes;
private Map<String, Object> checkBoxItems = new LinkedHashMap<String, Object>();
public String save() {
//save is not being executed....
return FORWARD;
}
public Map<String, Object> getCheckBoxItems() {
return checkBoxItems;
}
public void setCheckBoxItems(Map<String, Object> checkBoxItems) {
this.checkBoxItems = checkBoxItems;
}
public String[] getSelectedCheckBoxes() {
return selectedCheckBoxes;
}
public void setSelectedCheckBoxes(String[] selectedCheckBoxes) {
this.selectedCheckBoxes = selectedCheckBoxes;
}
}
When I click save it is giving the below message in <t:message for="chb"/>
"chb": Value is not a valid option.
Even though I did not add the required attribute for h:selectManyCheckbox, it is trying to validate or doing something else...
I've changed checkBoxItems variable type(with getter/setters) to List<SelectItem>, but it is not working as well.
What can be the reason, how can I solve it?
PS: I'm using JSF 1.1
You will get this error when the equals() test on a selected item has not returned true for any of the available items. So, when roughly the following happens under JSF's covers:
boolean valid = false;
for (Object availableItem : availableItems) {
if (selectedItem.equals(availableItem)) {
valid = true;
break;
}
}
if (!valid) {
// Validation error: Value is not valid!
}
That can in your particular case only mean that section.getObjectID() does not return a String which is what your selectedCheckboxes is declared to, but a different type or a custom type where equals() is not implemented or broken.
Update as per your comment, the getObjectID() returns Integer. It's thus been treated as String because selectedCheckBoxes is declared as String[]. You should change the following
private String[] selectedCheckBoxes;
private Map<String, Object> checkBoxItems = new LinkedHashMap<String, Object>();
to
private Integer[] selectedCheckBoxes;
private Map<String, Integer> checkBoxItems = new LinkedHashMap<String, Integer>();
and maybe (not sure, can't tell from top of head now) also explicitly supply a converter:
<h:selectManyCheckbox ... converter="javax.faces.Integer">
i didnt find any problem in th code, i thought there is the problem the list u passed to oneManyCheckBox.
hardcode some values in list in getter than check
public Map<String, Object> getCheckBoxItems() {
checkBoxItems.clear();
checkBoxItems.put("aaaa", "aaaa");
checkBoxItems.put("bbbb", "bbbb");
checkBoxItems.put("cccc", "cccc");
checkBoxItems.put("dddd", "dddd");
checkBoxItems.put("eeee", "eeee");
return checkBoxItems;
}
I'm working with a service which returns JSON which can be converted to Map (I'm using google-gson lib for converting). I need to get Set of values from this Map.
First, I had the next structure:
public Set<ProfileShow> getShows() {
String json = ...; //getting JSON from service
if (!Utils.isEmptyString(json)) {
Map<String, ProfileShow> map = Utils.fromJSON(json, new TypeToken<Map<String, ProfileShow>>() {
}.getType());
Set<ProfileShow> result = new HashSet<ProfileShow>();
for (String key : map.keySet()) {
result.add(map.get(key));
}
return result;
}
return Collections.emptySet();
}
public Set<Episode> getUnwatchedEpisodes() {
String json = ...; //getting JSON from service
if (!Utils.isEmptyString(json)) {
Map<String, Episode> map = Utils.fromJSON(json, new TypeToken<Map<String, Episode>>() {
}.getType());
Set<Episode> result = new HashSet<Episode>();
for (String key : map.keySet()) {
result.add(map.get(key));
}
return result;
}
return Collections.emptySet();
}
Utils.fromJSON method:
public static <T> T fromJSON(String json, Type type) {
return new Gson().fromJson(json, type);
}
As you can see, methods getShows() and getUnwatchedEpisodes() has the same structure. Only difference is a parametrized type of the returning Set. So, I decided to move getting Set from JSON to util method:
public static <T> Set<T> setFromJSON(String json, T type) {
if (!isEmptyString(json)) {
Map<String, T> map = fromJSON(json, new TypeToken<Map<String, T>>() {
}.getType());
Set<T> result = new HashSet<T>();
for (String key : map.keySet()) {
result.add(map.get(key));
}
return result;
}
return Collections.emptySet();
}
But now I'm stuck how to call this method in a proper way. Something like
Utils.setFromJSON(json, Episode.class.getGenericSuperclass()); //doesn't work
Thanks for your help.
Perhaps the easiest thing to do is change the type of type to Type, and pass in new TypeToken<Map<String, ProfileShow>>() { }.getType() or similar.
I guess you could construct the ParameterizedType if you really wanted.