Snakeyaml Dump java.time.LocalDateTime unable to dump the localdatetime - java-8

I am trying to write java.time.LocalTime property of an object to yaml file but it is not able to print it in YAML
public class YamlWriterTest {
public static void main(String[] args)
{
Map<String, Object> data = new LinkedHashMap<String, Object>();
data.put("name", "XyZ");
data.put("time", LocalDateTime.now());
data.put("date", new Date());
data.put("boolean", Boolean.TRUE);
Yaml yaml = new Yaml();
StringWriter writer = new StringWriter();
yaml.dump(data, writer);
System.out.println(writer.toString());
}
}
The output of above is coming as:
name: XyZ
time: !!java.time.LocalDateTime {}
date: 2022-11-24T20:14:35.781Z
boolean: true
How can I print the LocalDateTime object's value properly in YAML?
I was expecting the LocalDateTime object to be printed in yaml with its value

Related

Why are the application config values parsed by my custom Spring PropertySourceLoader not being used?

I am attempting to write a TOML PropertySourceLoader implementation. I took a look at some other examples on GitHub and stackoverflow, all of which seem to eventually parse the result out to a map and then return an OriginTrackedMapPropertySource, which is what I tried below:
public final class TomlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (!ClassUtils.isPresent("com.fasterxml.jackson.dataformat.toml.TomlFactory", null)) {
throw new IllegalStateException(
"Attempted to load " + name + " but jackson-dataformat-toml was not found on the classpath");
}
if (!ClassUtils.isPresent("com.fasterxml.jackson.core.io.ContentReference", null)) {
throw new IllegalStateException(
"Attempted to load " + name + " but jackson-core was either not found on the classpath or below version 2.13.0");
}
final ObjectMapper mapper = new ObjectMapper(new TomlFactory());
final Map<String, Object> resultMap = mapper.convertValue(mapper.readTree(resource.getInputStream()), new TypeReference<Map<String, Object>>(){});
return new OriginTrackedMapPropertySource(Optional.ofNullable(name).orElseGet(resource.getResource()::getFilename), resultMap);
}
}
public final class TomlPropertySourceLoader implements PropertySourceLoader {
#Override
public String[] getFileExtensions() {
return new String[]{"tml", "toml"};
}
#Override
public List<PropertySource<?>> load(final String name, final Resource resource) throws IOException {
final EncodedResource encodedResource = new EncodedResource(resource);
return Collections.singletonList(new TomlPropertySourceFactory().createPropertySource(name, encodedResource));
}
}
This code does seem to more or less do what is expected; it is executed when application.toml is present, it loads and parses the file out to a <String, Object> map, but from there, none of the actual properties seem to be present in the application — be it when using #Value, #ConfigurationProperties or even when attempting to set stuff like the port Tomcat runs on.
There's not a ton of information available on the internet, without digging into the depths of Spring, about what exactly it is expecting. I'm not sure if the problem is due to how my map is structured or perhaps due to something with the name.
Below you can find my application.toml file:
[spring.datasource]
url = "jdbc:hsqldb:file:testdb"
username = "sa"
[spring.thymeleaf]
cache = false
[server]
port = 8085
[myapp]
foo = "Hello"
bar = 42
aUri = "https://example.org/hello"
targetLocale = "en-US"
[myapp.configuration]
endpoints = ["one", "two", "three"]
[myapp.configuration.connectionSettings]
one = "hello"
[myapp.configuration.connectionSettings.two]
two_sub = "world!"
And my configuration classes:
#Data
#NoArgsConstructor
#Component
#ConfigurationProperties("myapp")
public class AppConfig {
private String foo;
private int bar;
private URI aUri;
private Locale targetLocale;
private SubConfiguration configuration;
}
#Data
#NoArgsConstructor
public class SubConfiguration {
private List<String> endpoints;
private Map<String, Object> connectionSettings;
}
As well as my testing controller:
#RestController
#RequiredArgsConstructor
public final class TomlDemoController {
private final AppConfig appConfig;
#GetMapping("/api/config")
AppConfig getConfig() {
return appConfig;
}
}
The issue was the structure of the property map. The keys have to be flattened in order to work. As an example, for a given table:
[server]
port = 8085
Rather than producing a nested map:
Properties = {
"server" = {
"port" = 8085
}
}
Spring is expecting something more like:
Properties = {
"server.port" = 8085
}
A quick solution using the ObjectToMapTransformer found in Spring Integration:
#Override
#SuppressWarnings("unchecked")
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (!ClassUtils.isPresent("com.fasterxml.jackson.dataformat.toml.TomlFactory", null)) {
throw new IllegalStateException(
"Attempted to load " + name + " but jackson-dataformat-toml was not found on the classpath");
}
if (!ClassUtils.isPresent("com.fasterxml.jackson.core.io.ContentReference", null)) {
throw new IllegalStateException(
"Attempted to load " + name + " but jackson-core was either not found on the classpath or below version 2.13.0");
}
final ObjectMapper mapper = new ObjectMapper(new TomlFactory());
final Message<JsonNode> message = new GenericMessage<>(mapper.readTree(resource.getInputStream()));
final ObjectToMapTransformer transformer = new ObjectToMapTransformer();
transformer.setShouldFlattenKeys(true);
Map<String,Object> resultMap = (Map<String, Object>) transformer.transform(message).getPayload();
return new OriginTrackedMapPropertySource(Optional.ofNullable(name).orElseGet(resource.getResource()::getFilename), resultMap);
}

Consider to dec lare/implement a mapping method: "java.lang.String map(java.lang.Object value)" during to use the Framework MapStruct

#Mapper(componentModel = "spring")
public interface MapperThings {
#MapMapping(keyTargetType = Object.class, valueTargetType = Object.class)
Map<String, String> toDto(Map<Object, Object> mapEntity);
List<Map <String, String>> toListDto(Collection<Map<Object, Object>> listEntity);
#MapMapping(keyTargetType = Object.class, valueTargetType = Object.class)
Map<Object, Object> toEntity(Map<String, String> mapDto);
List<Map<Object, Object> > toListEntity(Collection<Map<String, String>> listDto);
}
There is to generate without mistakes only :
#MapMapping(keyTargetType = Object.class, valueTargetType = Object.class)
Map<Object, Object> toEntity(Map<String, String> mapDto);
List<Map<Object, Object> > toListEntity(Collection<Map<String, String>> listDto);
I found temporary decision. But I would want to use the annotation #MapMapping.
#Mapper(componentModel = "spring")
public abstract class MapperMoviesAbstract {
public Map<String, String> toDto(Map<Object, Object> mapEntity) {
if(mapEntity == null) return null;
Map<String, String> stringMap = new HashMap<>();
for(Map.Entry<Object, Object> entry : mapEntity.entrySet()){
String key = (String) entry.getKey();
stringMap.put(key, mapEntity.get(key).toString());
}
return stringMap;
}
public abstract List< Map<String, String>> toListDto(Collection<Map<Object, Object>> listEntity);
}
According to the MapStruct documentation, using the #MapMapping annotation should generate a class that will perform the conversion.
But I get an error:
Can't map map key "java.lang.Object" to "java.lang.String ". Consider
to dec lare/implement a mapping method: "java.lang.String
map(java.lang.Object value)".
Do anyone have any ideas to do what?
The error message is telling you what to do. You need to provide a way to map Object into a String.
So you'll need a custom method like:
public String objectToString(Object object) {
// Your custom implementation
}

Spring Data Mongodb: json string to BasicDBObject

I've created this custom converter:
#Component
#WritingConverter
public class MetadataWriterConverter implements Converter<Metadata, DBObject> {
#Override
public DBObject convert(Metadata metadata) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", metadata.getName());
dbObject.put("metadata", (BasicDBObject) BasicDBObject.parse(reference.getMetadata()));
dbObject.removeField("_class");
return dbObject;
}
}
I'm getting this exception:
Caused by: org.bson.BsonInvalidOperationException: readStartDocument can only be called when CurrentBSONType is DOCUMENT, not when CurrentBSONType is ARRAY.
The problem is on:
(BasicDBObject) BasicDBObject.parse(metadata.getMetadata())
the content of metadata.getMetadata is: "[{'departament': 'JUST'}]".
Metadata class is:
public class Metadata {
private String id;
private String user;
private String metadata;
}
The content of metadata field is a json string, I'm trying to convert to BasicDbObject, but the problem appears when this string is an json array: [{},{}].
Guess:
Metadata met = new Metadata();
met.setId("Mdt1");
met.setUser("user");
met.setMetadata("[{'departament': 'JUST'}]");
What I want to get is:
{
"id": Mdt1,
"user": "user",
"metadata": [{"departament": "JUST"}]
}
Any ideas about how to refactor my converter?
Actually, BasicDBObject.parse() expects a JSONObject instead of a JSONArray that you are passing in your example. Check the docs here - http://api.mongodb.com/java/current/com/mongodb/BasicDBObject.html#parse-java.lang.String-
Instead, you can try converting your reference.getMetadata() into a valid JSON String and then using BasicDBList for your JSONArray. Something like below:
#Component
#WritingConverter
public class MetadataWriterConverter implements Converter<Metadata, DBObject>
{
#Override
public DBObject convert(Metadata metadata) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", metadata.getName());
String jsonString = String.format("{\"data\": " + reference.getMetadata() + "}");
BasicDBObject basicDBObject = (BasicDBObject) BasicDBObject.parse(jsonString);
BasicDBList parsedList = (BasicDBList) basicDBObject.get("data");
dbObject.put("metadata", parsedList);
dbObject.removeField("_class");
return dbObject;
}
}

Spring-boot MultipartFile issue with ByteArrayResource

I'm trying to implement a rest api consuming excel file. I'm using spring-boot and code is available here.
Code works fine when using FileSystemResource for payload. But i'm not able to make the code work with ByteArrayResource in replacement of FileSystemResource:
RestApi.java:
#RestController
public class RestApi {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#PostMapping("/api/upload")
public ResponseEntity<?> uploadFile(#RequestParam("file") MultipartFile uploadfile) {
LOGGER.debug("Single file upload!");
try {
LOGGER.info("\n\n ****** File name: {}, type {}! ************", uploadfile.getOriginalFilename(), uploadfile.getContentType());
this.processExcelFile(uploadfile.getInputStream());
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK);
}
private List<String> processExcelFile(InputStream stream) throws Exception {
List<String> result = new ArrayList<String>();
//Create Workbook instance holding reference to .xlsx file
try(XSSFWorkbook workbook = new XSSFWorkbook(stream);) {
//Get first/desired sheet from the workbook
XSSFSheet sheet = workbook.getSheetAt(0);
//Iterate through each rows one by one
Iterator<Row> rowIterator = sheet.iterator();
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
String cellValue = row.getCell(0).getRichStringCellValue().toString();
result.add(cellValue);
LOGGER.info("\n\n ****** Cell value: {} ************", cellValue);
}
return result;
}
}
}
RestApiTest:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RestApiTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private ResourceLoader loader;
#Test
public void testUploadFile() throws Exception {
Resource resource = this.loader.getResource("classpath:test.xlsx");
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
// parts.add("file", new FileSystemResource(resource.getFile()));
parts.add("file", new ByteArrayResource(IOUtils.toByteArray(resource.getInputStream())));
String response = this.restTemplate.postForObject("/api/upload", parts, String.class);
Assertions.assertThat(response).containsIgnoringCase("success");
}
}
I'm getting following error when running test:
java.lang.AssertionError:
Expecting:
<"{"timestamp":1487852597527,"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present","path":"/api/upload"}">
to contain:
<"success">
(ignoring case)
Any idea?
when using loader.getResource(...) you must use resource itself as answered above. So you don't need ByteArrayResource. I got this problem, but I'm not using resource from classpath. So if someone really need to use ByteArrayResource, here is my workaround
public class FileNameAwareByteArrayResource extends ByteArrayResource {
private String fileName;
public FileNameAwareByteArrayResource(String fileName, byte[] byteArray, String description) {
super(byteArray, description);
this.fileName = fileName;
}
#Override
public String getFilename() {
return fileName;
}
}
and then use it
parts.add("file", new FileNameAwareByteArrayResource("filename", byteArray));

Jersey REST service HashMap<String, ArrayList<String>> return

I want to return object of type HashMap> in GET method using XML or JSON. On client side I get keys of map, but values are null.
This is return class:
private HashMap<String, ArrayList<String>> hashMap = new HashMap<>();
public HashMap<String, ArrayList<String>> getHashMap() {
return hashMap;
}
public void setHashMap(HashMap<String, ArrayList<String>> hashMap) {
this.hashMap = hashMap;
}
This is GET method in service:
#GET
#Path("/mapa")
#Produces(MediaType.APPLICATION_XML)
public Test mapa() {
Test test = new Test();
HashMap<String, ArrayList<String>> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();
list.add("first");
list.add("second");
hashMap.put("first", list);
test.setHashMap(hashMap);
return test;
}
I get this in browser:
<test>
<hashMap>
<entry>
<key>first</key>
<value/>
</entry>
</hashMap>
</test>
Why is value empty?
Have you tried to use a Response instead of Test Class ?
#GET
#Path("/mapa")
#Produces(MediaType.APPLICATION_XML)
public Response mapa() {
Test test = new Test();
HashMap<String, ArrayList<String>> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();
list.add("first");
list.add("second");
hashMap.put("first", list);
test.setHashMap(hashMap);
return Response.status(200).entity(test).build();
}

Resources