How to serialize JsonNode - serialization

I am saving below instance to Aerospike Database.
My class which I want to serialize
public class School implements Serializable {
private static final long serialVersionUID = 1L;
private JsonNode studentInfo;
private JsonNode teacherInfo;
private void writeObject(ObjectOutputStream out) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue((OutputStream) out, studentInfo);
mapper.writeValue((OutputStream) out, teacherInfo);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ObjectMapper mapper = new ObjectMapper();
this.studentInfo = mapper.readValue((InputStream) in, JsonNode.class);
this.teacherInfo = mapper.readValue((InputStream) in, JsonNode.class);
}
}
Using above code, saving to Database is working fine(Serialization).
but when I try to get data from Database(Deserialization), I am facing below exception.
Caused by: com.aerospike.client.AerospikeException$Serialize: Error -10,1,30000,0,5,BB95B2FFB6EA79A 10.66.29.66 3030: ***com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input***
at [Source: java.io.ObjectInputStream#6ff29830; line: 1, column: 0]
at com.aerospike.client.command.Buffer.bytesToObject(Buffer.java:341)
at com.aerospike.client.command.Buffer.bytesToParticle(Buffer.java:69)
Please let me know if I am missing something.

I think we can't use ObjectOutputStream and ObjectInputStream more than one time.
I resolved it by writing writeObject and readObject function.
private void writeObject(ObjectOutputStream out) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = mapper.createArrayNode().add(this.studentInfo).add(this.teacherInfo);
mapper.writeValue((OutputStream) out, arrayNode);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = null;
arrayNode = mapper.readValue((InputStream) in, ArrayNode.class);
this.studentInfo = arrayNode.studentInfo;
this.teacherInfo = arrayNode.teacherInfo;
}
Or you can create a POJO with 2 fields as JsonNode and serialize the POJO.

Related

Spring WebSession Redis Exception

I want to store my WebSession in Redis. There is no problem at put operation, but it throws exception when retrieving stored record. Here is my example stack trace
Caused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array
to contain As.WRAPPER_ARRAY type information for class
java.lang.Object at [Source:
(byte[])"{"attributes":["org.springframework.session.web.server.session.SpringSessionWebSessionStore$SpringSessionMap",{}],"id":"2a5c3d9b-3557-4bd6-bca8-9e221c3a5b41","lastAccessTime":{"nano":800305900,"epochSecond":1605268779},"creationTime":{"nano":800305900,"epochSecond":1605268779},"expired":false,"maxIdleTime":{"seconds":5400,"nano":0,"negative":false,"zero":false,"units":["java.util.ImmutableCollections$List12",[["java.time.temporal.ChronoUnit","SECONDS"],["java.time.temporal.ChronoUnit","NANOS"]]]"[truncated
18 bytes]; line: 1, column: 1]
How could I solve this problem? I don't understand why it is happening? Thanks.
Here is my session service.
#Component
#RequiredArgsConstructor
#Slf4j
public class SessionMappingStorage {
private static final String USR_TO_SESSION___KEY = "USR_SESSION_MAP";
private final ReactiveHashOperations<String, String, Object> hashOps;
public Mono<Boolean> addSession(String username, WebSession session) {
return hashOps.put(USR_TO_SESSION___KEY, username, session);
}
public Mono<WebSession> getSessionByUserId(String username) {
return hashOps.get(USR_TO_SESSION___KEY, username).cast(WebSession.class);
}
}
Here is my redis configuration.
#Bean
public ReactiveRedisTemplate<String, String> reactiveRedisTemplate() {
RedisSerializer keySerializer, hashKeySerializer, hashValueSerializer;
keySerializer = hashKeySerializer = new StringRedisSerializer();
hashValueSerializer = new GenericJackson2JsonRedisSerializer(objectMapper());
RedisSerializationContext.RedisSerializationContextBuilder<String, String> builder =
RedisSerializationContext.newSerializationContext(keySerializer);
RedisSerializationContext<String, String> context =
builder.hashKey(hashKeySerializer).hashValue(hashValueSerializer).build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory(), context);
}
#Bean
public ReactiveHashOperations<String, String, Object> hashOps() {
return reactiveRedisTemplate().opsForHash();
}
private ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
return mapper;
}

Converting map to POJO with Jackson ObjectMapper throws exception

I have a MultivaluedMap<String, String> which represents the form parameters of a POST request. I'd like to convert a POJO from this class with only those fields I need for further processing. I found some answers which suggest using convertValue() from the Jackson ObjectMapper.
Convert a Map<String, String> to a POJO
public void process(MultivaluedMap<String, String> formParams) {
ObjectMapper objectMapper = new ObjectMapper();
final MyPojo myPojo = objectMapper.convertValue(formParams,MyPojo.class);
}
POJO
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyPojo {
private String status;
#JsonProperty("order_no")
private String orderId;
#JsonProperty("tid")
private String transactionId;
}
However this fails with the following exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: MyPojo["order_no"])
This is how the input looks like in the debugger:
I need to replace the String type with ArrayList<String> which seems to work:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyPojo {
private ArrayList<String> status;
#JsonProperty("order_no")
private ArrayList<String> orderId;
#JsonProperty("tid")
private ArrayList<String> transactionId;
}

Jackson deserialiser affected by the Deadbolt "restrict" annotation

I receiving an exception Could not resolve type id 'path.to.MyClass' as a subtype of [simple type, class java.lang.Object]: no such class found on play 2.7 server with DeadBolt (2.6.3 and 2.7.0) when I try deserialise JSON to Map<String, MyClass> inside of route action with a #Restrict annotation. All works fine if Remove this annotation.
MyClass.java
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "class")
public class MyClass implements Serializable {
public String name;
public Integer age;
public MyClass(){}
public MyClass(String name, Integer age){
this.name = name;
this.age = age;
}
}
serialise Map<String, MyClass>
Map<String, MyClass> value = new HashMap<>();
value.put("first", new MyClass("Bob",10));
value.put("second", new MyClass("Rob",20));
ObjectMapper mapper = Json.newDefaultMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "class");
String json = null;
try {
json = mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
output
{
"first":{
"class":"path.to.MyClass",
"name":"Bob",
"age":10
},
"second":{
"class":"path.to.MyClass",
"name":"Rob",
"age":20
}
}
JSON format looks so because of backward compatibility with old server which use old FlexJson.
deserialise
#Restrict({#Group({"Admin"})})
public CompletionStage<Result> action(long id) {
String json = getJsonFromStorage();
Map<String, MyClass> result = new HashMap<>();
try {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "class");
JsonFactory factory = mapper.getFactory();
JsonParser parser = factory.createParser(new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))));
JavaType type = mapper.getTypeFactory().constructType(result.getClass());
t = mapper.readValue(parser, type);
} catch (IOException e) {
e.printStackTrace();
}
return ok("ok")
}
I have a temporary solution. I override class loader for current thread to class loader from play.Environment
public class MyController extends Controller {
#Inject
private Environment environment;
#Restrict({#Group({"Admin"})})
public CompletionStage<Result> action(long id) {
Thread.currentThread().setContextClassLoader(environment.classLoader());
String json = getJsonFromStorage();
Map<String, MyClass> result = new HashMap<>();
try {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "class");
JsonFactory factory = mapper.getFactory();
JsonParser parser = factory.createParser(new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))));
JavaType type = mapper.getTypeFactory().constructType(result.getClass());
t = mapper.readValue(parser, type);
} catch (IOException e) {
e.printStackTrace();
}
return ok("ok")
}
}

How Test PUT RestController in Spring Boot

How can I test one PUT request with Spring Boot??
I have this method:
#RequestMapping(method = RequestMethod.PUT, value = "/")
public NaturezaTitulo save(#RequestBody NaturezaTitulo naturezaTitulo){
return naturezaTituloService.save(naturezaTitulo);
}
and this test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class NaturezaTituloControllerTest {
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private MockMvc mockMvc;
private HttpMessageConverter mappingJackson2HttpMessageConverter;
private List<NaturezaTitulo> naturezaTituloList = new ArrayList<>();
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();
Assert.assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext).build();
}
#Test
public void naturezaTituloNotFound() throws Exception {
mockMvc.perform(get("/naturezatitulo/55ce2dd6222e629f4b8d6fe0"))
.andExpect(status().is4xxClientError());
}
#Test
public void naturezaTituloSave() throws Exception {
NaturezaTitulo naturezaTitulo = new NaturezaTitulo();
naturezaTitulo.setNatureza("Testando");
mockMvc.perform(put("/naturezatitulo/").content(this.json(naturezaTitulo))
.contentType(contentType))
.andExpect(jsonPath("$.id", notNullValue()));
}
protected String json(Object o) throws IOException {
MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
this.mappingJackson2HttpMessageConverter.write(
o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
but I got this error:
java.lang.IllegalArgumentException: json can not be null or empty at
com.jayway.jsonpath.internal.Utils.notEmpty(Utils.java:259)
how can I pass one object from body in Put test?
tks

Deserialize JSON array into Map using Jackson in Java

I'm using fasterXML's Jackson (v2.3.3) library to deserialize and serialize a custom class. The class is defined as following:
public class Person {
private String name;
private Map<String, Person> children;
// lots of other fields of different types with no issues
}
the keys of map children are the name fields. I receive data in JSON with each person object structured as following (I have omitted the other fields):
{"name":"Bob", "children":[{"name":"Jimmmy"},{"name":"Judy"}]}
(Many Fields such as children are optional and aren't serialized when null)
I have been storing children in a List<Person> so far with no issues, but many new use cases need to have access to the set of names or to a specific Person using his name as key. This is why I have decided to store them using a Map.
After some research, I think the best way is to use Annotations #JsonDeserialize and #JsonSerialize with a JsonDeserializer and JsonSerializer as parameter respectively for the field children:
public class Person {
private String id;
#JsonSerialize(using=MySerializer.class)
#JsonDeserialize(using=MyDeserializer.class)
private Map<String, Person> friends;
// lots of other fields
}
My question is: Does such a JsonSerializer/JsonDeserializer exist and if not, how do I define one?
edit: I have started implementing a custom Deserializer, but I get this exception:
com.fasterxml.jackson.databind.JsonMappingException: Class has no default (no arg) constructor
which is weird because I have defined a default constructor. Here is my custom Deserializer:
public class MyDeserializer extends JsonDeserializer<Map<String, Person>> {
public MyDeserializer() {
}
#Override
public Map<String, Person> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode personsNodeArray = jp.getCodec().readTree(jp);
Map<String, Person> newChildren = null;
if (personsNodeArray.isArray() && personsNodeArray.size() > 0) {
newChildren = new HashMap<String, Person>();
for (JsonNode personNode : personsNodeArray) {
String id = personNode.get("name").asText();
// jsonMapper is a default ObjectMapper
newChildren.put(id, jsonMapper.readValue(personNode.toString(), Person.class));
}
}
return newChildren;
}
}
You can also consider reading children information as a collection of persons with subsequent conversion into a map. You can define a setter method (or a constructor parameter) to accept a List<Person> and then put each element into the Map<String, Person> children field. That would avoid unnecessary complexity of custom serialisation.
Here is an example:
public class JacksonChildren {
public static final String JSON = "{\"name\":\"Bob\", \"children\":[{\"name\":\"Jimmmy\"}," +
"{\"name\":\"Judy\"}]}";
public static class Person {
public String name;
private Map<String, Person> children = new HashMap<>();
public void setChildren(final List<Person> children) {
for (Person p : children) {
this.children.put(p.name, p);
}
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", children=" + children +
'}';
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, Person.class));
}
}
Output:
Person{name='Bob', children={Judy=Person{name='Judy', children={}}, Jimmmy=Person{name='Jimmmy', children={}}}}