How to deserialize Json data to private fields without public setters? - java

I've managed to deserialize Json data to my private variables using Jackson ObjectMapper, however, it makes use of public setters. I have also tried Gson, however I need to create a TypeAdapter which uses a switch statement (with package-private setters, which is atleast better than public setters) in its "read" method (breaking the OCP), as well as not following liskov substitution principle because it implements a TypeAdapter and its "write" function is left empty. Is there any way to deserialize in a way which does not use public setters or break lots of principles?
Sources for which may give a better understanding the implementation of my code:
https://www.tutorialspoint.com/gson/gson_custom_adapters.htm#
public class Food {
private String name;
private double calories;
#JsonAlias("serving_size_g")
private double servingSize;
public void setName(String name) {
this.name = name;
}
public double getCalories() {
return calories;
}
public void setCalories(double calories) {
this.calories = calories;
}
public double getServingSize() {
return servingSize;
}
}
And another class uses this function to create the food object:
private Food getFood(String foodName) throws JsonProcessingException {
String nutritionJsonString = apiConnector.getNutritionAsStringFromAPI(foodName);
Food food = objectMapper.readValue(nutritionJsonString, Food.class);
return food;
}
The other solution that I tried using Gson:
public class NutritionTypeAdapter extends TypeAdapter<Food> {
#Override
public void write(JsonWriter jsonWriter, Food food) throws IOException {
}
#Override
public Food read(JsonReader jsonReader) throws IOException {
final Food food = new Food();
jsonReader.beginObject();
while(jsonReader.hasNext()){
switch (jsonReader.nextName()){
case "name":
food.setName(jsonReader.nextString());
break;
case "calories":
food.setCalories(jsonReader.nextDouble());
break;
case "serving_size_g":
food.setServingSize(jsonReader.nextDouble());
break;
default:
jsonReader.skipValue();
}
}
jsonReader.endObject();
return food;
}
}

You can define constructor:
public class Main {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
try {
Food food = objectMapper.readValue("""
{
"name" : "x"
}
""", Food.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
static class Food {
#JsonCreator
public Food(
#JsonProperty("name")
String name,
#JsonProperty("calories")
double calories,
#JsonProperty("serving_size_g")
double servingSize
) {
// assign to fields
}
}
}```

Related

How to serialize with Gson an object that defines variable with same name as variable inherited from parent

I want to write a new class that extends another, and in the new class I want to define a variable with the same name as one in the parent. I can do this fine in Java since both are private but when I try to serialize the object with gson I get an error since there are two properties with the same name (even if the one inherited from the parent class is null and therefore should not be included in the json).
For example consider these classes:
public class Car {
private String color;
private Seat seat;
}
public class Seat {
private boolean isAdjustable;
}
and these classes:
public class FancyCar extends Car {
private FancySeat seat;
private boolean hasSpoiler;
}
public class Fancyseat extends Seat {
private boolean hasSeatWarmers;
}
with these classes I could create a new FancyCar with a seat that isAdjustable and hasSeatWarmers. But if I were to serialize with gson it would throw an exception as it tries to parse both the variables named seat even if the one inherited from Car is null.
Is there a better way to design these classes? Or a way to tell gson to ignore null fields altogether?
You can use #SerializedName(value = "fancySeat") Also, from Gson version.2.4 There is an option to alternate or have multiple name for serializedName when deserializing. No Need of CustomTypeadapter.
Gson Field Naming
Example for Multiple deserialization names
package com.test.practice;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
public class JsonSerializationExample {
public static void main(String[] args) {
// TODO Auto-generated method stub
Seat seatCar = new Seat();
seatCar.setAdjustable(true);
Fancyseat fancySeat = new Fancyseat();
fancySeat.setHasSeatWarmers(true);
fancySeat.setAdjustable(false);
Car car = new Car();
car.setColor("black");
car.setSeat(seatCar);
FancyCar fancyCar = new FancyCar();
fancyCar.setColor("white");
fancyCar.setSeat(fancySeat);
Gson gson = new Gson();
String jsonString = gson.toJson(fancyCar);
System.out.println("json :: "+jsonString);
}
}
class Car {
private String color;
private Seat seat;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Seat getSeat() {
return seat;
}
public void setSeat(Seat seat) {
this.seat = seat;
}
}
class Seat {
private boolean isAdjustable;
public boolean isAdjustable() {
return isAdjustable;
}
public void setAdjustable(boolean isAdjustable) {
this.isAdjustable = isAdjustable;
}
}
class FancyCar extends Car {
#SerializedName(value = "fancySeat")
private Fancyseat seat;
private boolean hasSpoiler;
public Fancyseat getSeat() {
return seat;
}
public void setSeat(Fancyseat seat) {
this.seat = seat;
}
public boolean isHasSpoiler() {
return hasSpoiler;
}
public void setHasSpoiler(boolean hasSpoiler) {
this.hasSpoiler = hasSpoiler;
}
}
class Fancyseat extends Seat {
private boolean hasSeatWarmers;
public boolean isHasSeatWarmers() {
return hasSeatWarmers;
}
public void setHasSeatWarmers(boolean hasSeatWarmers) {
this.hasSeatWarmers = hasSeatWarmers;
}
}

Java GSON - serialize int as strings to json file

I have this Java class:
class Car {
int mileage;
int id;
}
When I tell gson to serialize it, it of course serializes it to:
{
"mileage": 123,
"id": 12345678
}
But what if I want to serialize it to:
{
"mileage": "123",
"id": "12345678"
}
Assuming changing my members from int to String, is not an option, is there a way to tell gson to serialize those int members as strings to the json file?
There are likely many ways to achieve what you desire.
I will share two ways.
FIRST - Using Custom Serialization
SECOND - Using JsonAdapter Annotation - More Simple
Using a custom serialization
public static class CarSerializer implements JsonSerializer<Car> {
public JsonElement serialize(final Car car, final Type type, final JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("mileage", new JsonPrimitive(Integer.toString(car.getMileage())));
result.add("id", new JsonPrimitive(Integer.toString(car.getId())));
return result;
}
}
To call this, simply adapt your code or use the following code with a constructor
Car c = new Car(123, 123456789);
com.google.gson.Gson gson = new
GsonBuilder().registerTypeAdapter(Car.class, new CarSerializer())
.create();
System.out.println(gson.toJson(c));
The output should be
{"mileage":"123","id":"12345678"}
Full Code for Example 1:
public class SerializationTest {
public static class Car {
public int mileage;
public int id;
public Car(final int mileage, final int id) {
this.mileage = mileage;
this.id = id;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getMileage() {
return mileage;
}
public void setMileage(final String mileage) {
this.mileage = mileage;
}
}
public static class CarSerializer implements JsonSerializer<Car> {
public JsonElement serialize(final Car car, final Type type, final JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("mileage", new JsonPrimitive(Integer.toString(car.getMileage())));
result.add("id", new JsonPrimitive(Integer.toString(car.getId())));
return result;
}
}
public static void main(final String[] args) {
Car c = new Car(123, 123456789);
com.google.gson.Gson gson = new
GsonBuilder().registerTypeAdapter(Car.class, new CarSerializer())
.create();
System.out.println(gson.toJson(c));
}
}
Using a #JsonAdapter annotation
Use the JsonAdapter Annotation on the Car class
#JsonAdapter(CarAdapter.class)
public class Car {
public int mileage;
public int id;
}
Create the Custom Adapter
public class CarAdapter extends TypeAdapter<Car> {
#Override
public void write(JsonWriter writer, Car car) throws IOException {
writer.beginObject();
writer.name("mileage").value(car.getMileage());
writer.name("id").value(car.getId());
writer.endObject();
}
#Override
public Car read(JsonReader in) throws IOException {
// do something you need
return null;
}
}
To serialize, using this method, use something like this
Car c = new Car(123, 123456789);
Gson gson = new Gson();
String result = gson.toJson(c);
Printing result in this case should output
{"mileage":"123","id":"12345678"}
You may try it this way:
new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.registerTypeAdapter(Integer.class, (JsonSerializer<Integer>)
(integer, type, jsonSerializationContext) -> new
JsonPrimitive(String.valueOf(integer)))
.excludeFieldsWithoutExposeAnnotation().create();

Gson: illegalStateExcetpion: expected an int but NAME at line 1 column 3

I want to define my own serialize and deserialize methods for Student class, so I extended TypeAdapter and override its methods, but now deserialization does not work. Why this happens?
public class GSONFormat {
#Test
public void run()
{
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Student.class, new StudentAdapter());
Gson gson = builder.create();
Student s = new Student();
s.setAge(11);
s.setName("hiway");
System.out.println(gson.toJson(s));
String str = "{\"age\":11,\"name\":\"hiway\"}";
s = gson.fromJson(str, Student.class);
System.out.println(s);
}
}
class Student{
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class StudentAdapter extends TypeAdapter<Student>
{
#Override
public void write(JsonWriter out, Student s) throws IOException {
out.beginObject();
out.name("age");
out.value(s.getAge());
out.name("name");
out.value(s.getName());
out.endObject();
}
#Override
public Student read(JsonReader in) throws IOException {
in.beginObject();
Student s = new Student();
s.setAge(in.nextInt());
s.setName(in.nextString());
in.endObject();
return s;
}
}
As Matt Ball commented is not a good idea at all writing your type adapter if you do not have a very good reason at all. If you remove this line:
builder.registerTypeAdapter(Student.class, new StudentAdapter());
your parsing will work without any effort. However you might be interested in understand a bit further what you can do with type adapter. So I give you a possible implementation. Keep in mind that if your class Student get more complex, writing your own type adapter can be hard. It's better to let Gson do its own work. Anycase, here is something that patches your code (compiles under Java7, if you use lower version, change switch with an if chain).
class StudentAdapter extends TypeAdapter<Student>
{
#Override
public void write(JsonWriter out, Student s) throws IOException {
out.beginObject();
out.name("age");
out.value(s.getAge());
out.name("name");
out.value(s.getName());
out.endObject();
}
#Override
public Student read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
Student s = new Student();
while (in.peek() == JsonToken.NAME){
String str = in.nextName();
fillField(in, s, str);
}
in.endObject();
return s;
}
private void fillField(JsonReader in, Student s, String str)
throws IOException {
switch(str){
case "age": s.setAge(in.nextInt());
break;
case "name": s.setName(in.nextString());
break;
}
}
}

Serialize generic field from java object to json

I've a generic field in User.java. I want to use the value of T in json.
public class User<T> {
public enum Gender {MALE, FEMALE};
private T field;
private Gender _gender;
private boolean _isVerified;
private byte[] _userImage;
public T getField() { return field; }
public boolean isVerified() { return _isVerified; }
public Gender getGender() { return _gender; }
public byte[] getUserImage() { return _userImage; }
public void setField(T f) { field = f; }
public void setVerified(boolean b) { _isVerified = b; }
public void setGender(Gender g) { _gender = g; }
public void setUserImage(byte[] b) { _userImage = b; }
}
and mapper class is:
public class App
{
public static void main( String[] args ) throws JsonParseException, JsonMappingException, IOException
{
ObjectMapper mapper = new ObjectMapper();
Name n = new Name();
n.setFirst("Harry");
n.setLast("Potter");
User<Name> user = new User<Name>();
user.setField(n);
user.setGender(Gender.MALE);
user.setVerified(false);
mapper.writeValue(new File("user1.json"), user);
}
}
and the json output is :
{"field":{"first":"Harry","last":"Potter"},"gender":"MALE","verified":false,"userImage":null}
In the output, i want Name to be appeared in place of field. How do i do that. Any help?
I think what u ask is not JSON's default behavior. Field name is the "key" of the json map, not the variable name. U should rename the field or make some String process to do it.
private T field;
change the above to this:
private T name;
You need a custom serializer to do that. That's a runtime data transformation and Jackson has no support for data transformation other than with a custom serializer (well, there's wrapping/unwrapping of value, but let's not go there). Also, you will need to know in advance every type of transformation you want to apply inside your serializer. The following works:
public class UserSerializer extends JsonSerializer<User<?>> {
private static final String USER_IMAGE_FIELD = "userImage";
private static final String VERIFIED_FIELD = "verified";
private static final String FIELD_FIELD = "field";
private static final String NAME_FIELD = "name";
#Override
public void serialize(User<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
if (value.field instanceof Name) {
jgen.writeFieldName(NAME_FIELD);
} else {
jgen.writeFieldName(FIELD_FIELD);
}
jgen.writeObject(value.field);
jgen.writeStringField("gender", value._gender.name());
jgen.writeBooleanField(VERIFIED_FIELD, value._isVerified);
if (value._userImage == null) {
jgen.writeNullField(USER_IMAGE_FIELD);
} else {
jgen.writeBinaryField(USER_IMAGE_FIELD, value._userImage);
}
jgen.writeEndObject();
}
}

Json Jackson deserialization without inner classes

I have a question concerning Json deserialization using Jackson.
I would like to deserialize a Json file using a class like this one:
(taken from http://wiki.fasterxml.com/JacksonInFiveMinutes)
public class User
{
public enum Gender { MALE, FEMALE };
public static class Name {
private String _first, _last;
public String getFirst() { return _first; }
public String getLast() { return _last; }
public void setFirst(String s) { _first = s; }
public void setLast(String s) { _last = s; }
}
private Gender _gender;
private Name _name;
private boolean _isVerified;
private byte[] _userImage;
public Name getName() { return _name; }
public boolean isVerified() { return _isVerified; }
public Gender getGender() { return _gender; }
public byte[] getUserImage() { return _userImage; }
public void setName(Name n) { _name = n; }
public void setVerified(boolean b) { _isVerified = b; }
public void setGender(Gender g) { _gender = g; }
public void setUserImage(byte[] b) { _userImage = b; }
}
A Json file can be deserialized using the so called "Full Data Binding" in this way:
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(new File("user.json"), User.class);
My problem is the usage of the inner class "Name". I would like to do the same thing without using inner classes. The "User" class would became like that:
import Name;
import Gender;
public class User
{
private Gender _gender;
private Name _name;
private boolean _isVerified;
private byte[] _userImage;
public Name getName() { return _name; }
public boolean isVerified() { return _isVerified; }
public Gender getGender() { return _gender; }
public byte[] getUserImage() { return _userImage; }
public void setName(Name n) { _name = n; }
public void setVerified(boolean b) { _isVerified = b; }
public void setGender(Gender g) { _gender = g; }
public void setUserImage(byte[] b) { _userImage = b; }
}
This means to find a way to specify to the mapper all the required classes in order to perform the deserialization.
Is this possible? I looked at the documentation but I cannot find any solution.
My need comes from the fact that I use the Javassist library to create such classes, and it does not support inner or anonymous classes.
Thank you in advance
There should be no difference between the static inner class Name, and the top-level class of the same name. The Jackson runtime should not be able to meaningfully distinguish between the two situations.
Have you tried moving the Name class out of User, changing it into a top-level class? It should still work as before.
edit: I just tried this, and it works fine when Name is a top-level class. The example had it as an inner class for the sake of brevity, I suspect.
mr. Skaffman's answer is right on. The only additional thing to mention is that unlike JAXB, Jackson does not generally require you to specify classes you operate on, except for the root class (and not always even that, if you use Polymorphic Handling).

Categories

Resources