How to serialize an Object to Map by Moshi - moshi

I want to serialize an Object to Map by Moshi.Here is my codes by Gson
public static Map<String, String> toMap(Object obj, Gson gson) {
if (gson == null) {
gson = new Gson();
}
String json = gson.toJson(obj);
Map<String, String> map = gson.fromJson(json, new TypeToken<Map<String, String>>() {
}.getType());
return map;
}
And how to write by Moshi ?

Here's one way. Check out the toJsonValue doc here.
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Object> adapter = moshi.adapter(Object.class);
Object jsonStructure = adapter.toJsonValue(obj);
Map<String, Object> jsonObject = (Map<String, Object>) jsonStructure;
If you know the type of obj, it'd be better to look up the adapter of that type, rather than of Object. (The Object JsonAdadpter has to look up the runtime type on every toJson call.

#NanoJava8 solution crashes but can be made to work with a minor change using Map instead of HashMap
Type type = Types.newParameterizedType(Map.class, String.class, String.class);
JsonAdapter<Map<String,String>> adapter = moshi.adapter(type);
Map<String,String> map = adapter.fromJson(json);
As stated by Jesse in the answer Moshi support fields as Map but not HashMap.

In Kotlin:
val type = Types.newParameterizedType(
MutableMap::class.java,
String::class.java,
String::class.java
)
val adapter: JsonAdapter<Map<String, String>> = moshi.adapter(type)
val map: Map<String, String> = adapter.fromJson(responseJson)

Type type = Types.newParameterizedType(HashMap.class, String.class, String.class);
JsonAdapter<Map<String,String>> adapter = moshi.adapter(type);
Map<String,String> map = adapter.fromJson(json);

class HashMapJsonAdapter<K, V>(
private val keyAdapter: JsonAdapter<K>,
private val valueAdapter: JsonAdapter<V>
) : JsonAdapter<HashMap<K, V>>() {
#Throws(IOException::class)
override fun toJson(writer: JsonWriter, map: HashMap<K, V>?) {
writer.beginObject()
for ((key, value) in map ?: emptyMap<K, V>()) {
if (key == null) {
throw JsonDataException("Map key is null at ${writer.path}")
}
keyAdapter.toJson(writer, key)
valueAdapter.toJson(writer, value)
}
writer.endObject()
}
#Throws(IOException::class)
override fun fromJson(reader: JsonReader): HashMap<K, V>? {
val result = linkedMapOf<K, V>()
reader.beginObject()
while (reader.hasNext()) {
val name = keyAdapter.fromJson(reader)
val value = valueAdapter.fromJson(reader)
val replaced = result.put(name!!, value!!)
if (replaced != null) {
throw JsonDataException("Map key '$name' has multiple values at path ${reader.path} : $replaced and value")
}
}
reader.endObject()
return result
}
override fun toString(): String = "JsonAdapter($keyAdapter=$valueAdapter)"
companion object
}

Related

How can I read Flux<DataBuffer> content?

I want to read mulitpart/formdata, one part is application/JSON. I can't get them to Map<String,String>, Is there any way to parse Part to String?
private Map<String, String> getFormData(String path, MultiValueMap<String, Part> partMultiValueMap) {
if (partMultiValueMap != null) {
Map<String, String> formData = new HashMap<>();
Map<String, Part> multiPartMap = partMultiValueMap.toSingleValueMap();
for (Map.Entry<String, Part> partEntry : multiPartMap.entrySet()) {
Part part = partEntry.getValue();
if (part instanceof FormFieldPart) {
formData.put(partEntry.getKey(), ((FormFieldPart) part).value());
} else {
String bodyString = bufferToStr(part.content());
formData.put(partEntry.getKey(), bodyString);
}
}
return formData;
}
return null;
}
extra Flux
private String bufferToStr(Flux<DataBuffer> content){
AtomicReference<String> res = new AtomicReference<>();
content.subscribe(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
res.set(new String(bytes, StandardCharsets.UTF_8));
});
return res.get();
}
Subscribe is async; bufferToStr value may be null?
You could do it in non-blocking way with StringDecoder
Basically you could write your code to return Mono<Map<>>
Note: I'm using Pair class here to return key-value and later collect them to Map
Pair I'm using here is from package org.springframework.data.util.Pair
public Mono<Map<String, String>> getFormData(MultiValueMap<String, Part> partMultiValueMap) {
Map<String, Part> multiPartMap = partMultiValueMap.toSingleValueMap();
return Flux.fromIterable(multiPartMap.entrySet())
.flatMap(entry -> {
Part part = entry.getValue();
if (part instanceof FormFieldPart) {
return Mono.just(
Pair.of(entry.getKey(), ((FormFieldPart) part).value()) // return Pair
);
} else {
return decodePartToString(part.content()) // decoding DataBuffers to string
.flatMap(decodedString ->
Mono.just(Pair.of(entry.getKey(), decodedString))); // return Pair
}
})
.collectMap(Pair::getFirst, Pair::getSecond); // map and collect pairs to Map<>
}
private Mono<String> decodePartToString(Flux<DataBuffer> dataBufferFlux) {
StringDecoder stringDecoder = StringDecoder.textPlainOnly();
return stringDecoder.decodeToMono(dataBufferFlux,
ResolvableType.NONE,
MimeTypeUtils.TEXT_PLAIN,
Collections.emptyMap()
);
}

Java8 Streams: Remove an field from an object of the map value

I have a hash map like this
Map<String, AttributeValueUpdate> myMap = new HashMap<>;
The class AttributeValueUpdate looks like this:
public class AttributeValueUpdate {
private AttributeValue value;
private String action;
public static class Builder {
private AttributeValue value;
private String action;
public Builder() {
}
public AttributeValueUpdate.Builder withValue(AttributeValue value) {
this.value = value;
return this;
}
public AttributeValueUpdate.Builder withAction(String action) {
this.action = action;
return this;
}
protected void populate(AttributeValueUpdate instance) {
instance.setValue(this.value);
instance.setAction(this.action);
}
public AttributeValueUpdate build() {
AttributeValueUpdate instance = new AttributeValueUpdate();
this.populate(instance);
return instance;
}
}
}
The map has two entries
AttributeValueUpdate att1 = AttributeValueUpdate.builder().withAction("Add").withValue(new AttributeValue("sam").build();
AttributeValueUpdate att2 = AttributeValueUpdate.builder().withAction("Delete").withValue(new AttributeValue("john").build();
myMap.add("entry1", attr1);
myMap.add("entry2", atte2);
I want to modify mymap by deleting the "value field" from all the AttributeValueUpdate (which is value of the map), basically map's value field will be changed by removing "value field" of the AttributeValueUpdate object. How can I achieve this using java streams?
Java Stream API is not a friend with Map as long as it's collection-based (List, Set). You need to stream over the entries of the map.
As far as I understand, you want to remove (= make null) AttributeValue value of each AttributeValueUpdate instance (map's value). Here is the way to go assuming a constructor AttributeValueUpdate(String action):
Map<String, AttributeValueUpdate> updatedMap = myMap.entrySet().stream()
.map(entry -> {
String action = entry.getValue().getAction();
AttributeValueUpdate update = new AttributeValueUpdate(action);
return new SimpleEntry<>(entry.getKey(), update);
})
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
The easiest solution is using Map#replaceAll if you don't mind to mutate the map:
myMap.replaceAll((k, v) -> {
String action = v.getAction();
return new AttributeValueUpdate(action);
});

Autowire all interfaces keyed by type

I've a an interface and there are multiple implementations of the interface. There can be only one type of implementation for each interface type and I want to collect all the interface implementations per type, i.e.
Map<String, InterfaceExample>
public interface InterfaceExample {
String getType();
ClassA getClassA();
}
If I had to get in this form Map<String, List<InterfaceExample>> I would have done in the following way:
#Autowired
private List<InterfaceExample> interfaceExamples;
#Bean
public Map<String, List<IntefaceExample>> getExamples() {
return interfaceExamples.stream()
.map(x -> new AbstractMap.SimpleEntry<>(x.getType(), x))
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
}
Now that I've to ensure there is only one implementation per type I can do in following way:
#Bean
public Map<String, IntefaceExample> getExamples() {
Map<String, List<IntefaceExample>> examples = interfaceExamples.stream()
.map(x -> new AbstractMap.SimpleEntry<>(x.getType(), x))
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
Map<String, InterfaceExample> output = new HashMap<>();
examples.forEach((key, value) -> {
if(value.size() > 1) {
throw new RuntimeException("Wrongly configured!! ");
} else if(value.size() == 1) {
output.put(key, value.get(0));
}
});
return output;
}
Is there a different way to ensure that there is only implementation per type and create the bean in a "streamified way" without explicitly creating the output map?
After groupingby you can check if there are multiple beans of same type and collect them into List
List<InterfaceExample> res = interfaceExamples.stream().collect(Collectors.groupingBy(InterfaceExample::getType)).values()
.stream().map(value -> {
if (value.size() == 1) {
return value.get(0);
}
throw new RuntimeException("Wrongly configured!! ");
}).collect(Collectors.toList());
The best way is write a custom method that does the validation logic
public InterfaceExample conditionCheck(List<InterfaceExample> value) {
if (value.size() == 1) {
return value.get(0);
}
throw new RuntimeException("Wrongly configured!! ");
}
And then simply use stream
List<InterfaceExample> res = interfaceExamples.stream()
.collect(Collectors.groupingBy(InterfaceExample::getType))
.values()
.stream()
.map(this::conditionCheck)
.collect(Collectors.toList());

Fetch properties from Sonarqube via Sonarqube wsClient

I'd like to fetch sonar.timemachine.period1 via wsclient.
Seeing that it doesn't have one, I decided to bake one for myself
private Map<String, String> retrievePeriodProperties(final WsClient wsClient, int requestedPeriod) {
if (requestedPeriod > 0) {
final WsRequest propertiesWsRequestPeriod =
new GetRequest("api/properties/sonar.timemachine.period" + requestedPeriod);
final WsResponse propertiesWsResponsePeriod =
wsClient.wsConnector().call(propertiesWsRequestPeriod);
if (propertiesWsResponsePeriod.isSuccessful()) {
String resp = propertiesWsResponsePeriod.content();
Map<String, String> map = new HashMap<>();
map.put(Integer.toString(requestedPeriod), resp);
return map;
}
}
return new HashMap<>();
}
but it always return an empty Map<>
Any lead where I can go from this direction?
You can use org.sonar.api.config.Settings to fetch properties defined in SonarQube.

Java 8 retrieve Map's values into an array with stream and filter

Would someone help me with getting the array of the map values with stream and filtering?
public class TheMap extends HashMap<String, String> {
public TheMap(String name, String title) {
super.put("name", name);
super.put("title", title);
}
public static void main(final String[] args) {
Map<Long, Map<String, String>>map = new HashMap<>();
map.put(0L, null);
map.put(1L, new TheMap("jane", "engineer"));
map.put(2L, new TheMap("john", "engineer"));
map.put(3L, new TheMap(null, "manager"));
map.put(4L, new TheMap("who", null));
map.put(5L, new TheMap(null, null));
}
}
The result that I am looking for is an ArrayList<TheMap> with only these two entries:
TheMap("jane", "engineer")
TheMap("john", "engineer")
Basically, retrieve TheMap with none-null name and title.
List<Map<String, String>> list =
map.values().stream().filter(v ->
v != null &&
!v.entrySet().isEmpty() &&
!v.containsValue(null)).
collect(Collectors.toList());
If you need an arrayList of TheMap, try the following way:
ArrayList<TheMap> as = map.values()
.stream()
.filter(v -> v != null && v.get("name") != null && v.get("title") != null)
.map(m -> (TheMap)m)
.collect(Collectors.toCollection(ArrayList::new)));

Resources