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?
#Persisting Java objects to GSON
1 messages · Page 1 of 1 (latest)
<@&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>.
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.
- AI generated response
usually it's managed by annotations provided by the library, not transient keyword
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?
can you write out how, exactly, you would expect a circular reference to be handled?
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
okay but what would actually go into some-value-here
if my understanding's incorrect please let me know
there is the GSON issue, but also a conceptual one right now
{
"foo": {
"bar": {
"foo": {
"bar": ...
}
}
}
}
like
this can't do
exactly
okay so what do you actually want to happen
{
"foo": {
"bar": {}
}
}
The application I've inherited has these circular references
A browser interface to the Java compiler to experiment with the language
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?
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
err
or is there a better way, a catch all for example
there are multiple dimensions to the answer for that
i bet
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
and in terms of code structure/cleanliness?
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
you're thinking 15 years in the future of when this application written but yes, basically
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
is there a better way to persist java objects to json?
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"
gotcha
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
all that makes sense, thank you