#Persisting Java objects to GSON

1 messages · Page 1 of 1 (latest)

worthy vault
#

When trying to persist a java object to JSON, I'm running into stackoverflow exceptions because my application has some circular references to the same object. I've begun adding the 'transient' keyword to these circular fields but I quickly noticed I'm going to end up having 'transient' in many places. The application has existing save functionality using XML using XStream and they are specifying which fields to persist. Is there an equivalent for GSON to specify fields or will I need to have 'transient' scattered throughout my code base? Is there a best practice on saving large java objects to a json file/am I going about this incorrectly?

scenic nightBOT
#

<@&987246399047479336> please have a look, thanks.

#

Here is an AI assisted attempt to answer your question 🤖. Maybe it helps! In any case, a human is on the way 👍. To continue talking to the AI, you can use </chatgpt:1108714622413963314>.

#
TJ-Bot
When trying to persist a java object to JSON, I'm running into stackoverflow exceptions because my application has some circular references to the same object. I've begun adding the 'transient' keyword to these circular fields but I quickly noticed I'm goi

un using the transient keyword on the problematic fields in your object that are causing the circular references. This keyword tells GSON to ignore those fields during serialization, preventing the stackoverflow exception.

You can also create custom serialization and deserialization logic for your object using GSON's JsonSerializer and JsonDeserializer interfaces. Implementing these interfaces allows you to control how your object is serialized and deserialized, giving you more flexibility in handling circular references.

Another option is to use GSON's @Expose annotation on fields that you want to include in the JSON output. This annotation allows you to selectively include or exclude fields during serialization, helping you avoid circular references.

Overall, by carefully managing how your Java objects are serialized to JSON using techniques like using transient, custom serialization logic, and annotations like @Expose, you can effectively handle circular references and prevent stackoverflow exceptions when persisting objects with GSON.

plucky tide
#

usually it's managed by annotations provided by the library, not transient keyword

worthy vault
#

Ideally yes but I'm finding that Gson doesn't catch the circular references which makes the transient keyword required. unless i'm using the library wrong?

potent zinc
worthy vault
#

sure one sec

#
Class Foo {
  public Bar bar;
  public class Foo(Bar bar) {
      this.bar = bar;
  }
}

Class Bar {
   public transient Foo foo;
   public class Bar(Foo foo) {
       this.foo = foo;
   }
}

For context, I'm saving this to a .json file. So, I would expect the above to produce something similar to this

foo : {
   bar: some-value-here
}

The transient keyword found in the Bar class on the 'foo' field causes it not to create another Foo block in the JSON which would create a new Bar block in JSON which would create a new Foo block and so on
potent zinc
worthy vault
#

if my understanding's incorrect please let me know

potent zinc
#

there is the GSON issue, but also a conceptual one right now

#
{
  "foo": {
    "bar": {
      "foo": {
        "bar": ...
      }
    }
  }
}
#

like

#

this can't do

worthy vault
#

exactly

potent zinc
#

okay so what do you actually want to happen

worthy vault
#
{
  "foo": {
     "bar": {}
   }
}
#

The application I've inherited has these circular references

potent zinc
#

works fine for me

#
import com.google.gson.*;

static class Foo {
    public Bar bar;
}

static class Bar {
    public transient Foo foo;
}
void main() {
    Gson gson = new Gson();
    
    var foo = new Foo();
    var bar = new Bar();
    bar.foo = foo;
    foo.bar = bar;
    
    
    String s = gson.toJson(foo);
    System.out.println(s);
}
#

are you perhaps using an old GSON?

worthy vault
#

yeah transient does work and this is a solution. I guess my main question is: Is it a standard practice to have transient all throughout your class fields

potent zinc
#

err

worthy vault
#

or is there a better way, a catch all for example

potent zinc
#

there are multiple dimensions to the answer for that

worthy vault
#

i bet

potent zinc
#

dimension 1: What the hell is standard practice, anyways?

#

we get the term "industry standard" originally from a consulting firm that just worked with a lot of places and wrote down what they did

#

at this point, the term has no true meaning

worthy vault
#

and in terms of code structure/cleanliness?

potent zinc
#

so unless you are about to send out a survey and inspect a bunch of codebases its hard to know what people actually do

#

dimension 2: Its strange as hell to have circular references

#

not in general, but in a "serial form"

#

what this feels like, to me, is you are taking JPA entities and trying to directly turn them to JSON

worthy vault
#

you're thinking 15 years in the future of when this application written but yes, basically

potent zinc
#

which is likely where your problems lie

#

basically GSON and Jackson aren't actually "turn your object to JSON" things

#

not really

#

imagine a set

worthy vault
#

is there a better way to persist java objects to json?

potent zinc
#

no follow with me

#

so there is the whole universe of objects in java

#

every possible thing with all of the different possible invariants and guarentees you want to make

#

and all sorts of different relationships between fields

#

it just so happens that for objects that look mostly like "data carriers" taking the values of all the fields recursively tends to "work"

#

and doing the reverse also tends to "work"

#

and because doing this heuristic works most of the time, we have frameworks that default to it

#

and then there are hooks to override that - like adapters

#

but they are always "to the side" because "you only need them rarely"

worthy vault
#

gotcha

potent zinc
#

but the problem, in my view, with this system is that it breaks people's brains

#

instead of realizing that "yeah, this needs a more interesting serialization protocol than just scrape all the fields"

#

you - and many others - dive straight into "well, what if i mark the fields special?"

#

one solution to this is to simply write out the (maybe boring) code to custom adapt your type to json

#

but notice also how this is a function of TYPE to JSON

#

if you have a different context where you want to serialize the same TYPE in a different way, it gets annoying

#

another solution is to write out the (maybe boring) code to custom adapt your type to a type for which the "scrape all fields" approach is appropriate

#

so like

#

instead of

#
@Entity
public class Person {
   private Long id;
   private String name;
   // OOPS - circular!
   private List<Person> children;
}

// ...

@GET("/get_person/{id}")
public Person getPerson(long id) {
   return findById(id);
}
#

you would have

#
@Entity
public class Person {
   private Long id;
   private String name;
   // OOPS - circular!
   private List<Person> children;
}

// ...

public record GetPersonResult(
   long id,
   String name,
   List<Child> children
) {
   public record Child(long id, String name) {}

   public static GetPersonResult fromPerson(Person person) {
       return new GetPersonResult(
           person.getId(),
           person.getName(),
           person.getChildren().stream().map(child -> {
               return new Child(child.getId(), child.getName());
           }).toList()
       );
   }
}

@GET("/get_person/{id}")
public GetPersonResult getPerson(long id) {
   return GetPersonResult.fromPerson(findById(id));
}
#

which is more code, but you've turned it into something that is way more obvious how it functions

#

this is what the "DTO" stuff people talk about is

#

Instead of making a function from Person -> JSON, they make a function from Person -> GetPersonResult and rely on magic to get GetPersonResult -> JSON

#

dimension 3: perhaps time to use jackson instead

worthy vault
#

all that makes sense, thank you