Maintaining ordering of Class fields during Json Deserialization - java

Lets say I have a class as follows:
#JsonIgnoreProperties(ignoreUnknown = true)
class MyClass {
#JsonProperty(value="vertical")
private String vertical;
private String groupId;
#JsonProperty(value = "relationships")
private void unwrapGroupId(Map<String, Map<String, List<Map<String, Object>>>> relationships) {
this.groupId = ""; // Some logic to process the relationships map & set this.groupId based on the value set in this.vertical during deserialization
}
}
When deserializing an API Response to MyClass, is it guaranteed that vertical field is set before unwrapGroupId() is processed???? Else my processing in unwrapGroupId() would fail as this.vertical will be empty. If not , how can it be achieved.
I looked up #JsonPropertyOrder, but looks like it doesnt solve this usecase
Note: I use Jackson 2.8.1

Related

How to declare a variable or Object of any class type in Java

I am quite new to Java and I am trying to deserialize the JSON using Jackson and I facing some minor issue with regards to declaring the Object/Variable type. I will provide all the codes then explain the issue for easy understanding.
I have an enum that will have the required type values:
public enum IdentifierTypeValues {
Type1,
Type2,
Type3,
//Constructor and Getter of enum values
}
Then for each of these type, I have different classes which will have different input and do a completely different type of process:
public class GenerateType1 {
private String name;
private String age;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType2 {
private String address;
private String city;
private String country;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType3 {
private String firstName;
private String lastName;
private String fullName;
//Getter and Setter
//Some required process based on these values
}
Now I have a wrapper class for these type of classes which will take the type based on enum and typeInfo values. I want the typeInfo values to be any of the class based type something like this:
public class TypeSyntax {
private IdentifierTypeValues indeitiferType;
private GenerateType1 / GenerateType2 / GenerateType3 identifierTypeValues;
//Here the identifierTypeValues can have the values for anytype
//How to declare a variable of any of these class type?
}
This is the class that will be used by my JSON for deserializing. I know I can add a wrapper class of those 3 types and provide that wrapper class as a type class for this. Something like this:
public class WrapperClass{
private GenerateType1 type1;
private GenerateType2 type2;
private GenerateType3 type3;
}
public class TypeSyntax{
private IdentifierTypeValues indeitiferType;
private WrapperClass identifierTypeValues;
//But using this approach will change my JSON structure which I do not want to do.
}
My JSON structure is something like this and I would like to keep it in the same way.
{
"indeitiferType":"Type1",
"identifierTypeValues":{
"name":"Batman",
"age":"2008"
}
}
Is there a way I can declare the variable of multiple type class? or any better approach to handle this by keeping the json format same? I tried searching but I am unable to search what exactly so any help would be really appriciated.
Because the type identifier exists on a different level than the other properties a wrapper class TypeSyntax needed. There are several open feature requests to add wrapping functionality to Jackson e.g. https://github.com/FasterXML/jackson-databind/issues/512
Fortunately polymorphism is supported in Jackson with #JsonTypeInfo and #JsonSubTypes annotations.
Wrapper class should look like:
public class TypeSyntax {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "identifierType")
private GenerateTypeBase identifierTypeValues;
// getters and setters (omitted for brevity)
}
GenerateTypeBase is the common parent class
#JsonSubTypes({
#JsonSubTypes.Type(value = GenerateType1.class, name = "Type1"),
#JsonSubTypes.Type(value = GenerateType2.class, name = "Type2"),
})
public abstract class GenerateTypeBase {
private String name;
private String age;
// getters and setters (omitted for brevity)
}
In this different children classes will instantiated based on the identifierType property.
The children must extend this base class:
public class GenerateType2 extends GenerateTypeBase {
// additional properties
}
In a short test it will be:
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
GenerateType2 a = new GenerateType2();
a.setName("Foo");
a.setAge("13");
TypeSyntax w = new TypeSyntax();
w.setIdentifierTypeValues(a);
String json = mapper.writeValueAsString(w);
System.out.println(json);
}
and the output:
{
"identifierTypeValues":
{
"name":"Foo",
"age":"13"
},
"identifierType":"Type2"
}
Deserialization
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String input = "{\"identifierTypeValues\": \"name\":\"Foo\",\"age\":\"13\"},\"identifierType\":\"Type2\"}";
TypeSyntax w = mapper.readValue(new StringReader(input), TypeSyntax.class);
assertAll(
() -> assertEquals(GenerateType2.class, o.getIdentifierTypeValues().getClass()),
() -> assertEquals("13", o.getIdentifierTypeValues().getAge())
);
}
If you want more flexibility you can write custom (de)serializer and / or custom resolver. Using custom TypeIdResolver that will possible to convert identifiers to types programmatically instead of using "key-value pairs" in #JsonSubTypes

How to correctly map huge json file to Java (pojo)?

Large json files are available (from 100 MB to 2 GB).
How I can create java (pojo) from json? I need to get the names of all fields. For example, one class can have more than 1000 fields.
Tried the following libraries & online services:
JsonSchema2Pojo works well, but has a limit of about 50,000 characters at a time. Due to the presence of limits, I cannot completely map the class. Need to partially copy the json each time and then remove the duplicate fields.
http://www.jsonschema2pojo.org/
JsonToJava
https://github.com/astav/JsonToJava
Also tried several other online services. They also have limits or do not work correctly.
Example of json file:
{"type":"FeatureCollection","features":[
{"type":"Feature","geometry":{"type":"Point","coordinates":[27.5618791,53.902334]},"properties":{"ele":"280","name":"Минск","place":"city","capital":"yes","name:ar":"مينسك","name:be":"Мінск","name:bg":"Минск","name:bo":"མིན་སིཀ།","name:cs":"Minsk","name:cu":"Мѣньскъ","name:cv":"Минск","name:de":"Minsk","name:el":"Μινσκ","name:en":"Minsk","name:eo":"Minsko","name:es":"Minsk","name:et":"Minsk","name:fa":"مینسک","name:fi":"Minsk","name:fr":"Minsk","name:ga":"Minsc","name:gl":"Minsk - Мінск","name:he":"מינסק","name:hi":"मिन्‍स्‍क","name:hr":"Minsk","name:hu":"Minszk","name:hy":"Մինսկ","name:ia":"Minsk","name:io":"Minsk","name:is":"Minsk","name:it":"Minsk","name:ja":"ミンスク","name:ka":"მინსკი","name:kk":"Минск","name:kn":"ಮಿನ್ಸ್ಕ್","name:ko":"민스크","name:ku":"Mînsk","name:kv":"Минск","name:ky":"Минск","name:la":"Minscum","name:lt":"Minskas","name:lv":"Minska","name:mk":"Минск","name:ml":"മിൻസ്ക്","name:mr":"मिन्‍स्‍क","name:nl":"Minsk","name:no":"Minsk","name:oc":"Minsk","name:os":"Минск","name:pl":"Mińsk","name:pt":"Minsk","name:ru":"Минск","name:sk":"Minsk","name:sl":"Minsk","name:sr":"Минск","name:sv":"Minsk","name:ta":"மின்ஸ்க்","name:tg":"Минск","name:th":"มินสก์","name:tt":"Минск","name:ug":"مىنىسكى","name:uk":"Мінськ","name:ur":"منسک","name:vi":"Minxcơ","name:vo":"Minsk","name:yi":"מינסק","name:zh":"明斯克","website":"https://minsk.gov.by/","int_name":"Minsk","name:ast":"Minsk","name:ckb":"مینسک","name:csb":"Mińsk","name:jbo":"misk","name:mhr":"Минск","name:myv":"Минск ош","name:nds":"Minsk","name:pnb":"منسک","name:rue":"Мінск","name:sah":"Минскай","name:szl":"Mińsk","name:udm":"Минск","name:wuu":"明斯克","nat_name":"Мінск","old_name":"Менск","wikidata":"Q2280","wikipedia":"ru:Минск","population":"1982444","admin_level":"2","alt_name:be":"Менск","alt_name:vi":"Minsk;Minxcơva","name:prefix":"город","old_name:be":"Менск","addr:country":"BY","name:bat-smg":"Minsks","name:sr-Latn":"Minsk","wikipedia:be":"Мінск","wikipedia:en":"Minsk","wikipedia:pl":"Mińsk","addr:postcode":"220000","is_in:country":"Belarus","name:be-tarask":"Менск","source:name:oc":"Lo Congrès","is_in:continent":"Europe","population:date":"2018-01-01","capital_ISO3166-1":"yes","source:population":"Белстат","is_in:country_code":"BY"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5861178,53.9510657],[27.5861196,53.9510294],[27.5862319,53.9510313],[27.5862302,53.9510676],[27.5861178,53.9510657]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5885075,53.9512721],[27.5885398,53.9511614],[27.5887363,53.9511813],[27.588704,53.9512919],[27.5885075,53.9512721]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5881428,53.9512622],[27.5881704,53.9511135],[27.588421,53.9511296],[27.5883935,53.9512783],[27.5881428,53.9512622]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5892179,53.9516538],[27.5893274,53.9516011],[27.5893778,53.9516374],[27.5894352,53.9516098],[27.5895358,53.9516823],[27.5893689,53.9517626],[27.5892179,53.9516538]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.588949,53.9517486],[27.5891273,53.9516621],[27.5892764,53.9517686],[27.5890981,53.9518551],[27.588949,53.9517486]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5867595,53.9500825],[27.5867648,53.9499951],[27.5869291,53.9499985],[27.5869238,53.9500859],[27.5867595,53.9500825]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5830382,53.9503042],[27.5830422,53.9502013],[27.5832783,53.9502045],[27.5832743,53.9503074],[27.5830382,53.9503042]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5820906,53.9497446],[27.5820973,53.9496721],[27.5822371,53.9496765],[27.5822305,53.949749],[27.5820906,53.9497446]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5477403,53.8940388],[27.5482144,53.8939744],[27.5482459,53.8940791],[27.5482929,53.8940736],[27.5483083,53.8941424],[27.5482593,53.8941471],[27.5482741,53.8942048],[27.5485718,53.8943067],[27.5487113,53.8943028],[27.5487556,53.8943755],[27.548895,53.8944261],[27.5487985,53.894513],[27.5488079,53.8945841],[27.5483385,53.8943889],[27.548317,53.8943202],[27.5481439,53.8942549],[27.5481045,53.8941843],[27.5479863,53.8942063],[27.5479953,53.8942214],[27.5478248,53.8942506],[27.5477403,53.8940388]]]]},"properties":{"name":"Инженерный корпус метрополитена","building":"yes"}}
]}
Thanks in advance!
Thanks a lot for your advices.
Implemented in the following way:
I used JsonAnySetter annotation to set all fields instead creating pojo with all of the possible fields.
Here is my Dto's
FeatureDto
#Getter
#ToString
public class FeatureDto {
#JsonProperty("type")
private String type;
#JsonProperty("geometry")
private GeometryDto geometry;
#JsonProperty("properties")
private PropertiesDto properties;
}
PropertiesDto
#Getter
#ToString
public class PropertiesDto {
private List<FieldDto> fieldDtos = new ArrayList<>();
#JsonAnySetter
public void setProperties(String code, Object value)
{
if(value instanceof ArrayList) {
ArrayList<Object> list = (ArrayList<Object>) value;
StringBuffer stringBuffer = new StringBuffer();
list.forEach(o -> stringBuffer.append(o).append(", "));
fieldDtos.add(new FieldDto(code, stringBuffer.toString()));
return;
}
fieldDtos.add(new FieldDto(code, value.toString()));
}
}
FieldDto
#Data
#ToString
#JsonIgnoreProperties(ignoreUnknown = true)
#AllArgsConstructor
public class FieldDto {
#JsonProperty("code")
private String code;
#JsonProperty("value")
private String value;
}
Here is the json source:
{"type":"FeatureCollection","features":[
{"type":"Feature","geometry":{"type":"Point","coordinates":[27.5618791,53.902334]},"properties":{"ele":"280","name":"Минск","place":"city","capital":"yes","name:ar":"مينسك","name:be":"Мінск","name:bg":"Минск","name:bo":"མིན་སིཀ།","name:cs":"Minsk","name:cu":"Мѣньскъ","name:cv":"Минск","name:de":"Minsk","name:el":"Μινσκ","name:en":"Minsk","name:eo":"Minsko","name:es":"Minsk","name:et":"Minsk","name:fa":"مینسک","name:fi":"Minsk","name:fr":"Minsk","name:ga":"Minsc","name:gl":"Minsk - Мінск","name:he":"מינסק","name:hi":"मिन्‍स्‍क","name:hr":"Minsk","name:hu":"Minszk","name:hy":"Մինսկ","name:ia":"Minsk","name:io":"Minsk","name:is":"Minsk","name:it":"Minsk","name:ja":"ミンスク","name:ka":"მინსკი","name:kk":"Минск","name:kn":"ಮಿನ್ಸ್ಕ್","name:ko":"민스크","name:ku":"Mînsk","name:kv":"Минск","name:ky":"Минск","name:la":"Minscum","name:lt":"Minskas","name:lv":"Minska","name:mk":"Минск","name:ml":"മിൻസ്ക്","name:mr":"मिन्‍स्‍क","name:nl":"Minsk","name:no":"Minsk","name:oc":"Minsk","name:os":"Минск","name:pl":"Mińsk","name:pt":"Minsk","name:ru":"Минск","name:sk":"Minsk","name:sl":"Minsk","name:sr":"Минск","name:sv":"Minsk","name:ta":"மின்ஸ்க்","name:tg":"Минск","name:th":"มินสก์","name:tt":"Минск","name:ug":"مىنىسكى","name:uk":"Мінськ","name:ur":"منسک","name:vi":"Minxcơ","name:vo":"Minsk","name:yi":"מינסק","name:zh":"明斯克","website":"https://minsk.gov.by/","int_name":"Minsk","name:ast":"Minsk","name:ckb":"مینسک","name:csb":"Mińsk","name:jbo":"misk","name:mhr":"Минск","name:myv":"Минск ош","name:nds":"Minsk","name:pnb":"منسک","name:rue":"Мінск","name:sah":"Минскай","name:szl":"Mińsk","name:udm":"Минск","name:wuu":"明斯克","nat_name":"Мінск","old_name":"Менск","wikidata":"Q2280","wikipedia":"ru:Минск","population":"1982444","admin_level":"2","alt_name:be":"Менск","alt_name:vi":"Minsk;Minxcơva","name:prefix":"город","old_name:be":"Менск","addr:country":"BY","name:bat-smg":"Minsks","name:sr-Latn":"Minsk","wikipedia:be":"Мінск","wikipedia:en":"Minsk","wikipedia:pl":"Mińsk","addr:postcode":"220000","is_in:country":"Belarus","name:be-tarask":"Менск","source:name:oc":"Lo Congrès","is_in:continent":"Europe","population:date":"2018-01-01","capital_ISO3166-1":"yes","source:population":"Белстат","is_in:country_code":"BY"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5861178,53.9510657],[27.5861196,53.9510294],[27.5862319,53.9510313],[27.5862302,53.9510676],[27.5861178,53.9510657]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5885075,53.9512721],[27.5885398,53.9511614],[27.5887363,53.9511813],[27.588704,53.9512919],[27.5885075,53.9512721]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5881428,53.9512622],[27.5881704,53.9511135],[27.588421,53.9511296],[27.5883935,53.9512783],[27.5881428,53.9512622]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5892179,53.9516538],[27.5893274,53.9516011],[27.5893778,53.9516374],[27.5894352,53.9516098],[27.5895358,53.9516823],[27.5893689,53.9517626],[27.5892179,53.9516538]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.588949,53.9517486],[27.5891273,53.9516621],[27.5892764,53.9517686],[27.5890981,53.9518551],[27.588949,53.9517486]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5867595,53.9500825],[27.5867648,53.9499951],[27.5869291,53.9499985],[27.5869238,53.9500859],[27.5867595,53.9500825]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5830382,53.9503042],[27.5830422,53.9502013],[27.5832783,53.9502045],[27.5832743,53.9503074],[27.5830382,53.9503042]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5820906,53.9497446],[27.5820973,53.9496721],[27.5822371,53.9496765],[27.5822305,53.949749],[27.5820906,53.9497446]]]]},"properties":{"building":"yes"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[27.5477403,53.8940388],[27.5482144,53.8939744],[27.5482459,53.8940791],[27.5482929,53.8940736],[27.5483083,53.8941424],[27.5482593,53.8941471],[27.5482741,53.8942048],[27.5485718,53.8943067],[27.5487113,53.8943028],[27.5487556,53.8943755],[27.548895,53.8944261],[27.5487985,53.894513],[27.5488079,53.8945841],[27.5483385,53.8943889],[27.548317,53.8943202],[27.5481439,53.8942549],[27.5481045,53.8941843],[27.5479863,53.8942063],[27.5479953,53.8942214],[27.5478248,53.8942506],[27.5477403,53.8940388]]]]},"properties":{"name":"Инженерный корпус метрополитена","building":"yes"}}
]}

Make #JsonAnySetter work with #Value Lombok

I have a json object with a lot of properties (~80 properties) I want to deserialize in a POJO without creating manually all the properties. I was able to do this by using the #JsonAnySetter with a Map property like described here.
Now I want to make this work by making my POJO immutable using Lombok.
I tried this but it does only deserialize the id and code properties. Any idea on how to make it work?
#Value
#Builder
#EqualsAndHashCode
#JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {
#JsonProperty
private String id;
#JsonProperty
private String code;
#Getter(AccessLevel.NONE)
#Builder.Default
#JsonProperty
private Map<String, Optional<Object>> any = new HashMap<>();
#JsonAnyGetter
public Map<String, Optional<Object>> getAny(){
return this.any;
}
#JsonAnySetter
public void setAny(String key, Optional<Object> value){
this.any.put(key, value);
}
}
Update 2021-02-01: Lombok v1.18.16
Starting with v1.18.16, Lombok automatically copies #JsonAnySetter to the #Singular methods in builder. In combination with #Jacksonized you can simply use this code:
#Value
#Jacksonized
#Builder
class Product {
private String id;
private String code;
#JsonAnySetter
#Singular("any")
private Map<String, Object> any;
}
Older Lombok versions
For previous Lombok version, this requires some customization of the generated builder class.
Customizing a lombok builder can be done by simply adding its inner class header to your class. Lombok detects that there is already a builder class and just adds all the things that are not already present. This means you can add your own methods, and if those happen to have the same name than a method that lombok would generate, lombok skips this method.
With this approach, we replace the builder's setter method for "any", adding the required #JsonAnySetter to it. I use a LinkedHashMap as map in case the order is relevant; you can use a regular HashMap if it's not.
Furthermore, we replace the build() method to make sure the map you supply to the constructor is immutable. I use Guava's ImmutableMap here. This will make the created instance an immutable value.
#Value
#Builder
#JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {
#JsonProperty
private String id;
#JsonProperty
private String code;
#Getter(onMethod_ = #JsonAnyGetter)
private Map<String, Object> any;
#JsonPOJOBuilder(withPrefix = "")
public static class ProductBuilder {
#JsonAnySetter
public ProductBuilder any(String anyKey, Object anyValue) {
if (this.any == null) {
this.any = new LinkedHashMap<String, Object>();
}
this.any.put(anyKey, anyValue);
return this;
}
public Product build() {
return new Product(id, code, any == null ? ImmutableMap.of() : ImmutableMap.copyOf(any));
}
}
}

Sharing field constraints between server and client side

I am working on a project, where server side is based on Spring Boot 2, and front-end is based on Angular.
On the server side, in the data model classes I have declarations like that:
#Column(length = 256, nullable = false)
#Size(max = 256)
private String subject;
I would like to implement field length validation also on the front-end (angular side).
What is the best approach to share field length constraints between server and client side?
I do not like the idea that I need to repeat myself and hard-code field length on the both sides (server and client side).
Is it the best approach in my case if I declare set of constants like this:
private static final int maxSubjectLength = 256;
And use them as follows:
#Column(length = maxSubjectLength, nullable = false)
#Size(max = maxSubjectLength)
private String subject;
Then make a configuration class with these constants, which instance is accessible via GET http-request?
Or there is a better approach?
I decided to use the following approach. Assume we have a model class Question, that has a property body.
#Entity
#Table(name = "Questions")
public final class Question {
// other properties & code
private String body;
// other properties & code
}
And we want to limit the body length to 1024 symbols and define this limit only once, on the server and use this limit on the back-end and on the front-end of the application.
On the server side, in our model class Question we define static map, that contains size limits for all class properties.
#Entity
#Table(name = "Questions")
public final class Question {
private static class ModelConstraints {
static final int MAX_BODY_LENGTH = 1024;
// limits for other fields are going here
}
private static final Map<String, Integer> modelConstraintsMap;
static
{
final Map<String, Integer> localConstraintsMap = new HashMap<>();
localConstraintsMap.put("MAX_BODY_LENGTH", ModelConstraints.MAX_BODY_LENGTH);
// .... putting all constants from ModelConstraints to the map here
// composing unmodifable map
modelConstraintsMap = Collections.unmodifiableMap(localConstraintsMap);
}
#Column(length = ModelConstraints.MAX_BODY_LENGTH, nullable = false)
#Size(max = ModelConstraints.MAX_BODY_LENGTH)
private String body;
// other properties and code
public static Map<String, Integer> getModelConstraintsMap() {
return modelConstraintsMap;
}
// other properties and code
}
Internal class ModelConstraints contains definitions of max length values for all relevant model properties.
In the static block, I create an unmodifiable map, that contains these constraints and I return this map via public method.
In the controller, related to the model class I add a rest-endpoint that returns property length constraints.
#RequestMapping(path = "/questions/model-constraints", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<Map<String, Integer>> getModelConstraints() {
return new ResponseEntity<>(Question.getModelConstraintsMap(), HttpStatus.OK);
}
This method returns json-representation of the map with the property length constraints.
On the (angular) fron-tend I call this end-point and set maxlength property of form fields, related to the model class properties.
In the component typescript file I add and call this method:
loadConstraints() {
var url: string = "/questions/model-constraints";
this.http
.get(url)
.subscribe((data: Map<string, number>) => (this.modelConstraints = data));
}
And after call of this method the component property modelConstraints will contain map with field length constraints.
I set these constraints in the component template (html) file.
<textarea
matInput
rows="7"
placeholder="Question body"
maxlength="{{ modelConstraints['MAX_BODY_LENGTH'] }}"
[(ngModel)]="questionBody"
></textarea>
That's it. Using this approach you can define field length only once, on the server and use this definition on the server and on the client.

How to solve circular reference in json serializer caused by hibernate bidirectional mapping?

I am writing a serializer to serialize POJO to JSON but stuck in circular reference problem. In hibernate bidirectional one-to-many relation, parent references child and child references back to parent and here my serializer dies. (see example code below)
How to break this cycle? Can we get owner tree of an object to see whether object itself exists somewhere in its own owner hierarchy? Any other way to find if the reference is going to be circular? or any other idea to resolve this problem?
I rely on Google JSON To handle this kind of issue by using The feature
Excluding Fields From Serialization and Deserialization
Suppose a bi-directional relationship between A and B class as follows
public class A implements Serializable {
private B b;
}
And B
public class B implements Serializable {
private A a;
}
Now use GsonBuilder To get a custom Gson object as follows (Notice setExclusionStrategies method)
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
public boolean shouldSkipClass(Class<?> clazz) {
return (clazz == B.class);
}
/**
* Custom field exclusion goes here
*/
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
})
/**
* Use serializeNulls method if you want To serialize null values
* By default, Gson does not serialize null values
*/
.serializeNulls()
.create();
Now our circular reference
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
String json = gson.toJson(a);
System.out.println(json);
Take a look at GsonBuilder class
Jackson 1.6 (released september 2010) has specific annotation-based support for handling such parent/child linkage, see http://wiki.fasterxml.com/JacksonFeatureBiDirReferences. (Wayback Snapshot)
You can of course already exclude serialization of parent link already using most JSON processing packages (jackson, gson and flex-json at least support it), but the real trick is in how to deserialize it back (re-create parent link), not just handle serialization side. Although sounds like for now just exclusion might work for you.
EDIT (April 2012): Jackson 2.0 now supports true identity references (Wayback Snapshot), so you can solve it this way also.
Can a bi-directional relationship even be represented in JSON? Some data formats are not good fits for some types of data modelling.
One method for dealing with cycles when dealing with traversing object graphs is to keep track of which objects you've seen so far (using identity comparisons), to prevent yourself from traversing down an infinite cycle.
In addressing this problem, I took the following approach (standardizing the process across my application, making the code clear and reusable):
Create an annotation class to be used on fields you'd like excluded
Define a class which implements Google's ExclusionStrategy interface
Create a simple method to generate the GSON object using the GsonBuilder (similar to Arthur's explanation)
Annotate the fields to be excluded as needed
Apply the serialization rules to your com.google.gson.Gson object
Serialize your object
Here's the code:
1)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD})
public #interface GsonExclude {
}
2)
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
public class GsonExclusionStrategy implements ExclusionStrategy{
private final Class<?> typeToExclude;
public GsonExclusionStrategy(Class<?> clazz){
this.typeToExclude = clazz;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return ( this.typeToExclude != null && this.typeToExclude == clazz )
|| clazz.getAnnotation(GsonExclude.class) != null;
}
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(GsonExclude.class) != null;
}
}
3)
static Gson createGsonFromBuilder( ExclusionStrategy exs ){
GsonBuilder gsonbuilder = new GsonBuilder();
gsonbuilder.setExclusionStrategies(exs);
return gsonbuilder.serializeNulls().create();
}
4)
public class MyObjectToBeSerialized implements Serializable{
private static final long serialVersionID = 123L;
Integer serializeThis;
String serializeThisToo;
Date optionalSerialize;
#GsonExclude
#ManyToOne(fetch=FetchType.LAZY, optional=false)
#JoinColumn(name="refobj_id", insertable=false, updatable=false, nullable=false)
private MyObjectThatGetsCircular dontSerializeMe;
...GETTERS AND SETTERS...
}
5)
In the first case, null is supplied to the constructor, you can specify another class to be excluded - both options are added below
Gson gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(null) );
Gson _gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(Date.class) );
6)
MyObjectToBeSerialized _myobject = someMethodThatGetsMyObject();
String jsonRepresentation = gsonObj.toJson(_myobject);
or, to exclude the Date object
String jsonRepresentation = _gsonObj.toJson(_myobject);
If you are using Jackon to serialize, just apply #JsonBackReference to your bi-directinal mapping
It will solve the circular reference problem.
Note : #JsonBackReference is used to solve the Infinite recursion (StackOverflowError)
Used a solution similar to Arthur's but instead of setExclusionStrategies I used
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
and used #Expose gson annotation for fields which I need in the json, other fields are excluded.
if you are using spring boot,Jackson throws error while creating response from circular/bidirectional data, so use
#JsonIgnoreProperties
to ignore circularity
At Parent:
#OneToMany(mappedBy="dbApp")
#JsonIgnoreProperties("dbApp")
private Set<DBQuery> queries;
At child:
#ManyToOne
#JoinColumn(name = "db_app_id")
#JsonIgnoreProperties("queries")
private DBApp dbApp;
If you are using Javascript, there's a very easy solution to that using the replacer parameter of JSON.stringify() method where you can pass a function to modify the default serialization behavior.
Here's how you can use it. Consider the below example with 4 nodes in a cyclic graph.
// node constructor
function Node(key, value) {
this.name = key;
this.value = value;
this.next = null;
}
//create some nodes
var n1 = new Node("A", 1);
var n2 = new Node("B", 2);
var n3 = new Node("C", 3);
var n4 = new Node("D", 4);
// setup some cyclic references
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n1;
function normalStringify(jsonObject) {
// this will generate an error when trying to serialize
// an object with cyclic references
console.log(JSON.stringify(jsonObject));
}
function cyclicStringify(jsonObject) {
// this will successfully serialize objects with cyclic
// references by supplying #name for an object already
// serialized instead of passing the actual object again,
// thus breaking the vicious circle :)
var alreadyVisited = [];
var serializedData = JSON.stringify(jsonObject, function(key, value) {
if (typeof value == "object") {
if (alreadyVisited.indexOf(value.name) >= 0) {
// do something other that putting the reference, like
// putting some name that you can use to build the
// reference again later, for eg.
return "#" + value.name;
}
alreadyVisited.push(value.name);
}
return value;
});
console.log(serializedData);
}
Later, you can easily recreate the actual object with the cyclic references by parsing the serialized data and modifying the next property to point to the actual object if it's using a named reference with a # like in this example.
This is how i finally solved it in my case. This works at least with Gson & Jackson.
private static final Gson gson = buildGson();
private static Gson buildGson() {
return new GsonBuilder().addSerializationExclusionStrategy( getExclusionStrategy() ).create();
}
private static ExclusionStrategy getExclusionStrategy() {
ExclusionStrategy exlStrategy = new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes fas) {
return ( null != fas.getAnnotation(ManyToOne.class) );
}
#Override
public boolean shouldSkipClass(Class<?> classO) {
return ( null != classO.getAnnotation(ManyToOne.class) );
}
};
return exlStrategy;
}
Jackson provides JsonIdentityInfo annotation to prevent circular references. You can check the tutorial here.
This error can appened when you have two objects :
class object1{
private object2 o2;
}
class object2{
private object1 o1;
}
With using GSon for serialization, i have got this error :
java.lang.IllegalStateException: circular reference error
Offending field: o1
To solved this, just add key word transient :
class object1{
private object2 o2;
}
class object2{
transient private object1 o1;
}
As you can see here : Why does Java have transient fields?
The transient keyword in Java is used to indicate that a field should not be serialized.
If you use GSON to convert Java class in JSON you can avoid the fields that cause the circular reference and the infinitive loop, you only have to put the annotation #Expose in the fields that you want to appear in the JSON, and the fields without the annotation #Expose do not appear in the JSON.
The circular reference appears for example if we try to serialize the class User with the field routes of class Route, and the class Route have the field user of the class User, then GSON try to serialize the class User and when try to serialize routes, serialize the class Route and in the class Route try to serialize the field user, and again try to serialize the class User, there is a circular reference that provoke the infinitive loop. I show the class User and Route that mentioned.
import com.google.gson.annotations.Expose;
Class User
#Entity
#Table(name = "user")
public class User {
#Column(name = "name", nullable = false)
#Expose
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<Route> routes;
#ManyToMany(fetch = FetchType.EAGER)
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinTable(name = "like_", joinColumns = #JoinColumn(name = "id_user"),
inverseJoinColumns = #JoinColumn(name = "id_route"),
foreignKey = #ForeignKey(name = ""),
inverseForeignKey = #ForeignKey(name = ""))
private Set<Route> likes;
Class Route
#Entity
#Table(name = "route")
public class Route {
#ManyToOne()
#JoinColumn(nullable = false, name = "id_user", foreignKey =
#ForeignKey(name = "c"))
private User user;
To avoid the infinitive loop, we use the annotation #Expose that offer GSON.
I show in format JSON the result of serialize with GSON the class User.
{
"name": "ignacio"
}
We can see that the field route and likes do not exist in the format JSON, only the field name. Because of this, the circular reference is avoid.
If we want to use that, we have to create an object GSON on a specific way.
Gson converterJavaToJson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
In the end, we transform the java class of the model of hibernate user using the conversor GSON created.
User user = createUserWithHibernate();
String json = converterJavaToJson.toJson(user);
the answer number 8 is the better, i think so if you know what field is throwing a error the you only set the fild in null and solved.
List<RequestMessage> requestMessages = lazyLoadPaginated(first, pageSize, sortField, sortOrder, filters, joinWith);
for (RequestMessage requestMessage : requestMessages) {
Hibernate.initialize(requestMessage.getService());
Hibernate.initialize(requestMessage.getService().getGroupService());
Hibernate.initialize(requestMessage.getRequestMessageProfessionals());
for (RequestMessageProfessional rmp : requestMessage.getRequestMessageProfessionals()) {
Hibernate.initialize(rmp.getProfessional());
rmp.setRequestMessage(null); // **
}
}
To make the code readable a big comment is moved from the comment // ** to below.
java.lang.StackOverflowError [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain: com.service.pegazo.bo.RequestMessageProfessional["requestMessage"]->com.service.pegazo.bo.RequestMessage["requestMessageProfessionals"]
For example, ProductBean has got serialBean. The mapping would be bi-directional relationship. If we now try to use gson.toJson(), it will end up with circular reference. In order to avoid that problem, you can follow these steps:
Retrieve the results from datasource.
Iterate the list and make sure the serialBean is not null, and then
Set productBean.serialBean.productBean = null;
Then try to use gson.toJson();
That should solve the problem

Categories

Resources