Saving arrays of elements in the json object into different rows - json

While saving json object containing multiple jsons, the json object is being saved as a single array instead of multiple rows.
sample json:
[
{
"id" : 1, -- this is not a primary key and not unique but cannot be null
"name" : "John Doe",
"phone" : [
{ "type" : "home", "ref" : "111-111-1234"},
{ "type" : "work", "ref" : "222-222-2222"}
]
},
{
"id" : 2, -- this is not a primary key and not unique but cannot be null
"name" : "Jane Doe",
"phone" : [
{ "type" : "home", "ref" : "111-111-1234"},
{ "type" : "work", "ref" : "222-222-2222"}
]
}
]
This is what i need after saving in the database
id name phone
1 John Doe { "type" : "home", "ref" : "111-111-1234"}
1 John Doe { "type" : "work", "ref" : "222-222-2222"}
2 Jane Doe { "type" : "home", "ref" : "111-111-1234"}
2 Jane Doe { "type" : "work", "ref" : "222-222-2222"}
This is what I am getting
id name phone
1 John Doe [{ "type" : "home", "ref" : "111-111-1234"},{ "type" : "work", "ref" : "222-222-2222"}]
2 Jane Doe [{ "type" : "home", "ref" : "111-111-1234"},{ "type" : "work", "ref" : "222-222-2222"}]
here is how i am parsing the json object to pojo and saving to db
#Entity
#Table(name="person")
public class person{
private Integer id;
private String name;
private String phone;
#Transient
JsonNode phoneJson;
private static OhjectMapper mapper = new ObjectMapper();
getter/setter
#Transient
public JsonNode getPhoneJson(){
return phoneJson;
}
public void setPhoneJson(JsonNode phoneJson){
this.phoneJson = phoneJson;
}
#JsonIgnore
#Column(name="phone")
public String getPhone() throws Exception{
return mapper.writeValueAsString(phoneJson);
}
public void setPhone(String phone) throws Exception{
this.phone = mapper.readTree(phone);
}
}
dao- save
personRepository.save(person)
any help would be appreciated.
UPDATE
Multiple jSON Column
[
{
"id" : 1, -- this primary key and not unique but cannot be null
"name" : { --this element can be empty/null
"first" : "John",
"last" : "Doe"
},
"phone" : [
{ "type" : "home", "ref" : 1111111234},
{ "type" : "work", "ref" : 2222222222}
]
},
{
"id" : 2, -- this primary key and not unique but cannot be null
"name" : {
"first" : "Jane",
"last" : "Doe"
},
"phone" : [
{ "type" : "home", "ref" : 1111111234},
{ "type" : "work", "ref" : 2222222222}
]
}
]
how do i get result as below
id name phone
1 [{John},{Doe}] { "type" : "home", "ref" : "111-111-1234"}
1 [{John},{Doe}] { "type" : "work", "ref" : "222-222-2222"}
2 [{Jane},{Doe}] { "type" : "home", "ref" : "111-111-1234"}
2 [{Jane},{Doe}] { "type" : "work", "ref" : "222-222-2222"}

You need to duplicate Person objects n times where n is size of phone array. To make it clear, I propose, to create two separate models which we can use separately for parsing JSON and saving in DB. Below you can find simple example which:
Parses JSON to List<JsonPerson>
Converts List<JsonPerson> to List<Person>
Prints List<Person> (you can save it to DB)
Example:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
CollectionType personsType = mapper.getTypeFactory().constructCollectionType(List.class, JsonPerson.class);
// parse
List<JsonPerson> jsonPersons = mapper.readValue(jsonFile, personsType);
// convert
List<Person> persons = jsonPersons.stream()
.map(p -> p.mapTo(mapper))
.flatMap(List::stream)
.collect(Collectors.toList());
persons.forEach(System.out::println);
// save persons to DB
// ...
}
}
class JsonPerson {
private Integer id;
private String name;
private ArrayNode phone;
public List<Person> mapTo(ObjectMapper mapper) {
List<Person> persons = new ArrayList<>();
phone.elements().forEachRemaining(phone -> {
persons.add(map(mapper, phone));
});
return persons;
}
private Person map(ObjectMapper mapper, JsonNode p) {
Person person = new Person();
person.setId(id);
person.setName(name);
try {
person.setPhone(mapper.writeValueAsString(p));
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
return person;
}
// getters, setters, toString
}
class Person {
private Integer id;
private String name;
private String phone;
// getters, setters, toString
}
Above code prints:
Person{id=1, name='John Doe', phone='{"type":"home","ref":"111-111-1234"}'}
Person{id=1, name='John Doe', phone='{"type":"work","ref":"222-222-2222"}'}
Person{id=2, name='Jane Doe', phone='{"type":"home","ref":"111-111-1234"}'}
Person{id=2, name='Jane Doe', phone='{"type":"work","ref":"222-222-2222"}'}
Above code separates parsing JSON from other parts. Also, do not create ObjectMapper in each POJO. POJO should not know anything about ObjectMapper and Jackson.
Update
Because name is JSON Object you can create new POJO - Name with first and last properties or treat it similarly to phone and deserialise to JsonNode:
class JsonPerson {
private Integer id;
private JsonNode name;
private ArrayNode phone;
public List<Person> mapTo(ObjectMapper mapper) {
List<Person> persons = new ArrayList<>();
phone.elements().forEachRemaining(phone -> {
persons.add(map(mapper, phone));
});
return persons;
}
private Person map(ObjectMapper mapper, JsonNode p) {
Person person = new Person();
person.setId(id);
person.setName(getNameAsString());
try {
person.setPhone(mapper.writeValueAsString(p));
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
return person;
}
private String getNameAsString() {
if (name == null) {
return null;
}
StringBuilder builder = new StringBuilder();
if (name.isObject()) {
ObjectNode nameObject = (ObjectNode) name;
builder.append("[");
builder.append("{").append(nameObject.get("first")).append("}");
builder.append(",");
builder.append("{").append(nameObject.get("last")).append("}");
builder.append("]");
}
return builder.toString();
}
// getters, setters, toString
}
After change above code should print:
Person{id=1, name='[{"John"},{"Doe"}]', phone='{"type":"home","ref":1111111234}'}
Person{id=1, name='[{"John"},{"Doe"}]', phone='{"type":"work","ref":2222222222}'}
Person{id=2, name='[{"Jane"},{"Doe"}]', phone='{"type":"home","ref":1111111234}'}
Person{id=2, name='[{"Jane"},{"Doe"}]', phone='{"type":"work","ref":2222222222}'}
getNameAsString method is simplified, you need to handle all corner cases and create String representation better for null, empty and semi-empty nodes.

Related

Can't serialize Json to Java object using Jackson ObjectMapper

I'm writing a Java spring boot mvc application with possibility of data export / import. I wrote a wrapper class, that shoud serialize / deserialize data for Student class. It works good for export, but during import an error appears
com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name
'student' does not match expected ('students') for type [simple type,
class org.bajiepka.courseApp.wrappers.Response] at [Source:
(FileInputStream); line: 2, column: 3]
here is my maven jackson dependency:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
I use wrapper class Response for list of students,
package org.bajiepka.courseApp.wrappers;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import org.bajiepka.courseApp.domain.Student;
#JsonRootName("import")
public class Response {
private Iterable<Student> students;
#JsonProperty("students")
public Iterable<Student> getStudents(){
return students;
}
public void setStudents(Iterable<Student> students) {
this.students = students;
}
}
And this is my Student class:
#Data
#Entity
public class Student {
private #Id #GeneratedValue Long id;
private #Version int version;
#NotNull
private String name;
private String address;
private String phone;
private Integer gradeBook;
private float averageProgress;
public Student() {}
}
}
This is the method that created an export file:
public String write(boolean toFile){
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
try {
String result = writer.writeValueAsString(response);
if (toFile){
result = fileService.writeToFile(result);
}
return result;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return "";
}
after export i get the file with json, that i can't convert to java objects:
{
"student" : [ {
"id" : 1,
"version" : 0,
"name" : "Rachel Jessica Parker",
"address" : "Pentaho",
"phone" : "111-22-33",
"gradeBook" : 1000121,
"averageProgress" : 0.0
}, {
"id" : 2,
"version" : 0,
"name" : "Bobby Jackson Junior",
"address" : "Illinois",
"phone" : "222-33-44",
"gradeBook" : 1000122,
"averageProgress" : 0.0
}, {
"id" : 3,
"version" : 0,
"name" : "Sammy Smith Carlson",
"address" : "Pennsylvania",
"phone" : "333-44-55",
"gradeBook" : 1000123,
"averageProgress" : 0.0
}, {
"id" : 4,
"version" : 0,
"name" : "Harry Dale Harrison",
"address" : "Detroit",
"phone" : "444-55-66",
"gradeBook" : 1000124,
"averageProgress" : 0.0
}, {
"id" : 5,
"version" : 0,
"name" : "Lindsey jefferson Conly",
"address" : "Washington",
"phone" : "555-66-77",
"gradeBook" : 1000125,
"averageProgress" : 0.0
}, {
"id" : 6,
"version" : 0,
"name" : "Mo Williams Jr.",
"address" : "New York",
"phone" : "666-77-88",
"gradeBook" : 1000126,
"averageProgress" : 0.0
} ]
}
And finally this is the method for conversion:
#GetMapping(value = "/import")
public String importFile(#RequestParam Long id){
File importFile = new File(exchangeFileService.findById(id).getName());
if (importFile.exists()) {
try (FileInputStream stream = new FileInputStream(importFile)) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
TypeReference<Response> typeReference = new TypeReference<>(){};
Response response = mapper.readValue(stream, typeReference);
} catch (IOException e) {
e.printStackTrace();
}
}
return "redirect:/exchange/upload";
}
i get mentioned above MismatchInputException at mapper.readValue(stream, typeReference)
ObjectMapper should return me a Response with a list of students but it doesn't...
#UPDATE
I've managed to find the cause of the error.
During the Object -> Json serialization jackson root name is missed...
I've added manually
{
**"import" : {**
"student" : [ {
"id" : 1,
"version" : 0,
"name" : "Rachel Jessica Parker",
"address" : "Pentaho",
"phone" : "111-22-33",
"gradeBook" : 1000121,
"averageProgress" : 0.0
}, ... ]
,
"course" : [ {
"id" : 7,
"version" : 0,
"name" : "Physics for 9-th grade",
"number" : 100292910,
"cost" : 25000.0,
"modules" : 0,
"max_COURSES_PER_STUDENT" : 3,
"modules_PER_COURSE" : 10
}, ... ]
**}**
}
Also i've managed to extend Response class, as it was required... Now i'm trying to find the reason in ObjectMapper during serialization...
The problem was caused due to Java object -> Json serialization without root level.
To add it to Json i've just added it at mapper by withRootName() method and everything works well from now on!
mapper.writer().withDefaultPrettyPrinter().withRootName("import")

JSON Loading Lazy properties

I have 2 entities
Product:
#Id #GeneratedValue
private Long id;
private String name;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Product parentProduct;
#OneToMany(fetch = FetchType.LAZY)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<Product> childProduct;
#OneToMany(mappedBy="product", fetch = FetchType.LAZY)
#JsonManagedReference #JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<Image> images;
Image:
#Id
#GeneratedValue
private Long id;
private String type;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JsonBackReference
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Product product;
the lazy relationship are being loaded when I call from a RestController but when I call from my main method (Spring Boot) they came empty.
What I do to maintain lazy when Json serialize.
json return:
[ {
"id" : 1,
"name" : "Passatempo Pacote",
"description" : "Cookies Package",
"images" : [ {
"id" : 2,
"type" : "png"
}, {
"id" : 1,
"type" : "jpeg"
} ]
}, {
"id" : 2,
"name" : "Passatempo",
"description" : "Cookies",
"parentProduct" : {
"id" : 1,
"name" : "Passatempo Pacote",
"description" : "Cookies Package",
"images" : [ {
"id" : 2,
"type" : "png"
}, {
"id" : 1,
"type" : "jpeg"
} ]
}
} ]
Images must be empty because lazy config on property
The Json Serializer will call the get method, which will load your lazy field.
If you don't want these lazy field in json, you could annotate them #JsonIgnore.
#JsonInclude(JsonInclude.Include.NON_EMPTY) means the field will be ignored only if it's empty.
Use fetch = FetchType.EAGER instead of fetch = FetchType.LAZY.

Jackson fasterxml mapper - serialize/deserialize map of list of interface/abstract object type

I have searched and searched but found no answer/solution so am resorting to asking this myself.
Using fasterxml jackson 2.8.7
I have a complex map that I pass to angular and get back via a Spring mapping as a JSON string.
Map:
Map<String, List<TableModel>> tableEntryData = new LinkedHashMap<>();
It is a Map of List of TableModel. Table model is an interface that extends into several concrete classes. So using TableModel as the object type allows me to put whatever concrete class extended from TableModel into the List.
I can pass this to angular fine using:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
tableEntryJSON = mapper.writeValueAsString( tableEntryData );
But when I try to deserialize the JSON:
tableEntryData = mapper.readValue(tableEntryJSON,
new TypeReference<LinkedHashMap<String, LinkedList<TableModel>>>() {} );
I am facing this exception:
com.fasterxml.jackson.databind.JsonMappingException: (was
java.lang.NullPointerException) (through reference chain:
java.util.LinkedHashMap["Table_A_List"]->java.util.LinkedList[8])
Now my data Map (tableEntryData) contains data like this:
Map = {
[Table_A_List] = {LinkedList of 13 objects of TableA},
[Table_B_List] = {LinkedList of 2 objects of TableB}
}
Where Table_A_List and Table_B_List are map keys. TableA and TabelB are classes that implement interface TableModel.
The JSON created is like this:
{ "TABLE_A_List" : [ {
"deserializeType" : "TableA",
"amount" : 0.0, }, {
"deserializeType" : "TableA",
"amount" : 8.3, }, {
"deserializeType" : "TableA",
"amount" : 20.0, }, {
"deserializeType" : "TableA",
"amount" : 19.4, }, {
"deserializeType" : "TableA",
"amount" : 33.9, }, {
"deserializeType" : "TableA",
"amount" : 11.3, }, {
"deserializeType" : "TableA",
"amount" : 23.6, },{
"deserializeType" : "TableA",
"amount" : 2.6, },{
"deserializeType" : "TableA",
"amount" : 3.6, },{
"deserializeType" : "TableA",
"amount" : 23.0, },{
"deserializeType" : "TableA",
"amount" : 230.6, },{
"deserializeType" : "TableA",
"amount" : 23.8, },{
"deserializeType" : "TableA",
"amount" : 11.1, }, ],
"TABLE_B_List" : [ {
"deserializeType" : "TableB",
"code" : 9, }, {
"deserializeType" : "TableB",
"code" : 1, }, ] }
which seems correct.
In order to correctly deserialize concrete classes from the interface, I have defined things as follows
TableModel:
#JsonDeserialize(using = ModelInstanceDeserializer.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public interface TableModel
{
....
}
TableA:
#JsonDeserialize(as = TableA.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class TableA implements Serializable
{
String deserializeType = "TableA"; //This is for providing type info that helps with the deserialization since JSON mapper erases type info inside Maps/Lists
//public getters for fields
}
TableB:
#JsonDeserialize(as = TableB.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class TableB implements Serializable
{
String deserializeType = "TableB"; //This is for providing type info that helps with the deserialization since JSON mapper erases type info inside Maps/Lists
//public getters for fields
}
Then I created a deserializer class ModelInstanceDeserializer as well:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class ModelInstanceDeserializer extends JsonDeserializer<TableModel> {
#Override
public TableModel deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends TableModel> instanceClass = null;
String type = "";
if (root.has("deserializeType")) {
type = root.get("deserializeType").asText();
}
if (type.equals("TableA")) {
instanceClass = TableA.class;
} else if (type.equals("TableA")) {
instanceClass = TableB.class;
}
if (instanceClass == null) {
return null;
}
return mapper.readValue(jp, instanceClass);
}
}
Now while this baby works somewhat, there is a problem. The deserializer reads the second map entry as a List that is part of the first map entry. What is happening is that the deserializer seems to read two records at a time from the TABLE_A_List JSON and as a result ends up including TABLE_B_List on the 7th entry:
{
"TABLE_B_List" : [ {
"deserializeType" : "TableB",
"code" : 9,
}, {
"deserializeType" : "TableB",
"code" : 1,
}, ]
}
There are 13 total entries in TABLE_A_List.
So can anybody point me on how to do this correctly. I think I need to configure the deserializer better or something else is the issue.
Yes I can always go back and get rid of the TableModel approach and use something better but that is not the point. It would require too much code change at this point. I need to make this work correctly.
Found the solution a few minutes later despite having lost my brains over this for 2 days.
Use:
return mapper.readValue(root.toString(), instanceClass);
Instead of:
return mapper.readValue(jp, instanceClass);
readValue on JsonParser ends up incrementing the root node. It was a logical error + lack of bloody guides.
Thanks everyone for your answers! :*

JSON not returning columns in Spring Data REST

I am trying out examples for Spring Data REST however the JSON object returned in my testing does not return the column names (which were earlier 'PUT') and just returns the links to the objects. What could be wrong?
Scenario:
Entity: 'User'
#Entity
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String guid;
private String fullName;
private String email;
}
Repository: UserRepository (Exposed as REST service)
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends JpaRepository<User, Long> {
}
REST 'PUT' request to create a USER object:
REST GET Call to get the JSON response for the User object (the problem)
No id, Guid or email is returned in the JSON response.
Removing lombok's #Data annotation made all basic properties appear in the JSON response. Looks like a side effect of lombok.
I had the same problem, but I was not using autogenerated Lombok classes.
I found a solution. The problem in what I was experiencing was that only links appear in the json output of a Spring Data Repository.
For example:
{
"_embedded": {
"users": [{
"_links": {
"self": {
"href": "http://localhost:8080/users/1"
},
"user": {
"href": "http://localhost:8080/users/1"
}
}
}, {
"_links": {
"self": {
"href": "http://localhost:8080/users/2"
},
"user": {
"href": "http://localhost:8080/users/2"
}
}
}, {
"_links": { ...
The Solution:
Add getters and setters to the Entity, one for each variable you want to show in the response. Once you add getters and setters, you will be able to run the application again, and get json responses that contain values for each of your variables.
In one of the other answers, the workaround was to remove Lombok #Data annotation. Lombok was not generating getters and setters for the class using that annotation in time, so, in turn, none of the returned JSON responses contained any initialized values.
The outcome looks better:
{
"_embedded" : {
"users" : [ {
"username" : "admin",
"firstName" : "Administrator",
"lastName" : "Administrator",
"roles" : [ { "roleName" : "ROLE_ADMIN", "description" : "Administrator"
} ],
"_links" : { "self" : { "href" : "http://localhost:8080/users/1" },
"user" : { "href" : "http://localhost:8080/users/1" }
}
}, {
"username" : "bobby", ...

Multiple models to json

Is there a way to create a json array string from multiple models for Windows 8.1 Store App. For example:
public class Foo
{
public string property1 {get;set;}
public string property2 {get;set;}
}
public class Foo2
{
public string value1 {get;set;}
public string value2 {get;set;}
}
I have multiple models. I'm using json.net to convert models to json string. I can also convert arrays of models.
My question is how can I create an array json string using these models like:
[
{
"property1":"string",
"property2":"string"
},
{
"value1":"string",
"value2":"string"
},
{
"property1":"string",
"property2":"string"
}
]
I'm really stuck and need your help.
One way to do this is to create abstract class BaseFoo that both Foo and Foo2 classes will inherit from:
public abstract class BaseFoo
{
}
public class Foo : BaseFoo
{
public string property1 { get; set; }
public string property2 { get; set; }
}
public class Foo2 : BaseFoo
{
public string value1 { get; set; }
public string value2 { get; set; }
}
Lets say that you initialize the list like this:
var list = new List<BaseFoo>
{
new Foo
{
property1 = "prop11",
property2 = "prop12"
},
new Foo2
{
value1 = "val1",
value2 = "val2"
},
new Foo
{
property1 = "prop21",
property2 = "prop22"
}
};
The serialization should look like this:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
var json = JsonConvert.SerializeObject(list, Formatting.Indented, settings);
And the produced json will look like this:
[
{
"$type": "ConsoleApplication23.Foo, ConsoleApplication23",
"property1": "prop11",
"property2": "prop12"
},
{
"$type": "ConsoleApplication23.Foo2, ConsoleApplication23",
"value1": "val1",
"value2": "val2"
},
{
"$type": "ConsoleApplication23.Foo, ConsoleApplication23",
"property1": "prop21",
"property2": "prop22"
}
]
The TypeNameHandling.Objects option is added for successful deserialization:
var deserializedList = JsonConvert.DeserializeObject<List<BaseFoo>>(json, settings);
You can exclude it and get the exact json string you need, if you do not need to deserialize it back. Although leaving it included shouldn't break any functionality but will increase the size of the json string.