How can I return child objects in a many-to-many relationship with Dropwizard/Hibernate/Swagger without recursion? - jackson

I'm using Dropwizard and Swagger to create a REST API to manage access to various objects. Two of these objects are involved in a many-to-many relationship, for example:
public class Puppy implements Serializable {
private Long id;
private String name;
#ManyToMany(targetEntity = Trick.class)
#JoinTable(
name="puppies_tricks",
joinColumns=#JoinColumn(name="puppy_id"),
inverseJoinColumns=#JoinColumn(name="trick_id"))
private List<Trick> tricks;
#JsonProperty("tricks")
public List<Trick> getTricks() { return this.tricks; }
...
}
public class Trick implements Serializable {
private Long id;
private String name;
#ManyToMany(targetEntity = Puppy.class)
#JoinTable(
name="puppies_tricks",
joinColumns=#JoinColumn(name="trick_id"),
inverseJoinColumns=#JoinColumn(name="puppy_id"))
private List<Puppy> puppies;
#JsonProperty("puppies")
public List<Puppy> getPuppies() { return this.puppies; }
...
}
And suppose the actual data looks something like:
# Tricks:
[{ id: 1, name: 'Roll over' },
{ id: 2, name: 'Play dead' },
{ id: 3, name: 'Steal second' }]
# Puppies:
[{ id: 1, name: 'Flopsy' },
{ id: 2, name: 'Mopsy' },
{ id: 3, name: 'Cottontail' }]
# Puppies_Tricks
[{ puppy_id: 1, trick_id: 1 },
{ puppy_id: 1, trick_id: 2 },
{ puppy_id: 2, trick_id: 2 },
{ puppy_id: 2, trick_id: 3 }]
So when I GET /puppy/1 I want to get a structure like:
{
"id": 1,
"name": "Flopsy",
"tricks": [
{ "id": 1, "name": "Roll over"},
{ "id": 2, "name": "Play dead" } ]
}
but what I'm actually getting is a nested array:
{
"id": 1,
"name": "Flopsy",
"tricks": [
{ "id": 1, "name": "Roll over", "puppies": [ { "id": 1, "name": "Flopsy" }, ...},
{ "id": 2, "name": "Play dead", "puppies": [...] } ]
}
Is there some way to tell Jackson/Dropwizard to stop descending through tricks to when getting puppies, and vice versa?
Dropwizard 1.0.6, Java 1.8.

Jackson has support of object-graph serialization through the #JsonManagedReference #JsonBackReference annotations. See the documentation here. You can find a simple example here.
You should really read more carefully the documentation of the tools you're using.
There is already an answer that summarizes what approaches are available.

#JsonIgnore
#JsonProperty("puppies")
public List<Puppy> getPuppies() { return this.puppies; }
will stop infinite loop

My solution here was to create a separate PuppyCore class that implements serialization but isn't persisted. This includes basic Puppy info (id, name). Similarly a TrickCore has id and name only.
Then Puppy includes Set tricks, with this getter:
public Set<TrickCore> getTricks();
which returns a list of TrickCore objects – these don't have the Puppy list, and so halts the recursion. A similar pattern on Trick returns Set<PuppyCore> for getPuppies().

Related

How to returning only as string column in json response object from eloquent relationship using eager loading

I'm trying to figure out how to eager load data as a string instead of array object from a related table.
I have 3 models and here are the relations
Product.php
class Product extends Model
{
public function ProductTag()
{
return $this->hasMany(ProductTag::class);
}
public function Category()
{
return $this->belongsTo(Category::class);
}
}
ProductTag.php
class ProductTag extends Model
{
public function Product()
{
return $this->belongsTo(Product::class);
}
}
Category.php
class Category extends Model
{
public function Product()
{
return $this->hasMany(Product::class);
}
}
I've tried doing it like so:
public function tag(){
return $this->hasMany(ProductTag::class)
->selectRaw('GROUP_CONCAT(tag) as tag,id')
->groupBy('id');
}
public static function list(){
$result = Category::with(['Product'=>function($q){
$q->with(['tag'=>function($q1){
$q1->first();
}]);
}])->get();
}
Here is the reponse:
{
"data": {
"categories": [
{
"id": 1,
"category": "test 1",
"product": [
{
"id": 46,
"name": "test prod 1",
"tag": []
},
{...}
]
},
{
"id": 2,
"category": "test 2",
"product": [
{
"id": 45,
"name": "test prod 2",
"tag": [
{
"product_tag": "Test1, test12, test123"
}
]
},
{...}
]
},
{
"id": 3,
"category": "test 3",
"product": []
}
]
}
}
The Response is as expected except tag array so, instead of an array named "tag" can I get "product_tag" within the "product" array
"product": [
{
"id": 45,
"name": "test prod 2",
"tag": [
{
"product_tag": "Test1, test12, test123"
}
]
}
]
Here is what I want it to look like:
"product": [
{
"id": 45,
"name": "test prod 2",
"product_tag": "Test1, test12, test123"
}
]
Is it possible and any smart way of doing this in Laravel using Eloquent?
Simple :)
Btw, if you can - rename product_tag in response to tag_line or same - it's not right way to take same name for relation and mutator.
class Product extends Model
{
public function getTagLineAttribute()
{
//tag - is "name" field in ProductTag
return $this->ProductTag->pluck('tag')->implode(',');
}
public function ProductTag()
{
return $this->hasMany(ProductTag::class);
}
public function Category()
{
return $this->belongsTo(Category::class);
}
}
//eager loading with tags
Product::with('ProductTag')->find(..)->tagLine;

Use a projection to modify only one field but keeping default for all others

First of all I'd like to say I love what i've seen so far from Spring Data JPA and Spring Data REST. Thanks a lot to all people involved.
Problem description
I have an entity model similar to the classes below. One parent and two different child entities referencing the parent als a ManyToOne Assoziation. For one of the childs i like to have the default rendering of all its properites and links as it is when no projection is applied to the parent.
The other child should be mapped to a simple string array containing only the id or some specific field.
Code and example JSONs
#Entity
public class Parent {
#Id
private Long id;
private String parentValue;
#OneToMany(mappedBy = "parent")
private List<Child1> child1;
#OneToMany(mappedBy = "parent")
private List<Child2> child2;
// ... getters and setters
}
#Entity
public class Child1 {
#Id
private Long id;
private String child1Value;
#ManyToOne
Parent parent;
// ... getters and setters
}
#Entity
public class Child2 {
#Id
private Long id;
#ManyToOne
Parent parent;
}
the response when getting the collection resource of parent is this
{
"_embedded": {
"parents": [
{
"parentValue": "Parent1",
"child1": [
{
"child1Value": "Child1",
"_links": {
"parent": {
"href": "http://localhost:8080/parents/1"
}
}
}
],
"child2": [
{
"_links": {
"parent": {
"href": "http://localhost:8080/parents/1"
}
}
}
],
// removed remaining json to shorten the example
}
But what i like to achieve is the following JSON
{
"_embedded": {
"parents": [
{
"parentValue": "Parent1",
"child1": [
{
"child1Value": "Child1",
"_links": {
"parent": {
"href": "http://localhost:8080/parents/1"
}
}
}
],
"child2": [1],
What i tried so far
Added an excerptProjection to the ParentRepository:
#RepositoryRestResource(excerptProjection = ParentRepository.ArrayProjection.class)
public interface ParentRepository extends PagingAndSortingRepository<Parent, Long>{
public interface ArrayProjection {
String getParentValue();
List<Child1> getChild1();
#Value("#{target.child2.![id]}")
List<Long> getChild2();
}
}
Edited: In the first version of the question, the Projection was incorrect regarding the return type of getChild1(), as it should return the complete collection not only one element. Thanks #kevvvvyp for still trying to help.
The result is similar to what i want, but the links on the Child1 property are missing now:
{
"_embedded": {
"parents": [
{
"child2": [
2
],
"child1": {
"child1Value": "Child1"
},
"parentValue": "Parent1",
// removed remaining json to shorten example
Also the approach with the excerptProjection means i'd have to change the projection everytime the entity changes. Which probalby won't happen to much but that means somebody will forget to change it in the future ;-)
I think a projection is the right way to go here, what about...
interface ArrayProjection {
#JsonIgnore
#Value("#{target}")
Parent getParent();
default String getParentValue(){
return this.getParent().getParentValue();
}
default Child1 getChild1(){
//TODO what are we actually trying to return here? Given we join the parent to
// the child on id & id is the primary key on both entities can we ever get more than one child?
return CollectionUtils.firstElement(this.getParent().getChild1());
}
default List<Long> getChild2() {
return this.getParent().getChild2().stream()
.map(Child2::getId)
.collect(Collectors.toList());
}
}
Response...
GET http://localhost:8080/api/parents
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 31 Mar 2021 21:08:54 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"_embedded": {
"parents": [
{
"parentValue": "Parent1",
"child1": {
"child1Value": "Child1"
},
"child2": [
1
],
"_links": {
"self": {
"href": "http://localhost:8080/api/parents/1"
},
"parent": {
"href": "http://localhost:8080/api/parents/1{?projection}",
"templated": true
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/parents"
},
"profile": {
"href": "http://localhost:8080/api/profile/parents"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
Response code: 200; Time: 713ms; Content length: 724 bytes
In reponse to your concern, if we code using default method's we will see a compile error when that entity changes. It might be possible to use a class projection instead.
Also I would consider if we really want to return a projection by default... it might confuse a client who then tries to create/update a parent (this is perhaps why you've kept the id's hidden by default?).
Sample - https://github.com/kevvvvyp/sdr-sample
Update
Another (more complex) way to acheive this could be to make use of Jackson's SimpleBeanPropertyFilter, you could customise the JSON response based on some value (the entity class, a value in a particular field, an annotation etc).

Inserting data in vuex-orm database that is already normalized

Assuming the data i receive is already normalized, or at least the relations.
How can this data be inserted into the vuex-orm database?
Example JSON data:
{
"carmodel": [
{
"id": 1,
"title": "M3",
"manufacturer_id": 1
},
{
"id": 2,
"title": "a-class"
"manufacturer_id": 2
}
],
"manufacturer": [
{
"id": 1,
"title": "BMW"
},
{
"id": 2,
"title": "Mercedes"
}
]
}
Manufacturer Model and Carmodel are inserted like this:
Manufacturer.insert({ data: response.data.manufacturer })
CarModel.insert({ data: response.data.carmodel })
This example model will not work:
import { Model } from '#vuex-orm/core'
import Manufacturer from '#/models/Manufacturer'
export default class CarModel extends Model {
static entity = 'carModels'
static fields () {
return {
id: this.attr(null),
title: this.string(''),
manufacturer: this.hasOne(Manufacturer, 'manufacturer_id')
}
}
}
Ok, i think i got it. Instead of this.hasOne i have to use belongsTo and use the manufacturer_id from the same model:
import { Model } from '#vuex-orm/core'
import Manufacturer from '#/models/Manufacturer'
export default class CarModel extends Model {
static entity = 'carModels'
static fields () {
return {
id: this.attr(null),
title: this.string(''),
manufacturer_id: this.attr(null),
manufacturer: this.belongsTo(Manufacturer, 'manufacturer_id')
}
}
}

Nested relationships wagtail api

I have the following data structure:
{"related_cases": [
{
"type": "related_case_block",
"value": {
"case": 13,
"short_text": "Case 2 is related!"
},
"id": "3aec5efe-55dc-441f-aa5c-fbbb801d237a"
}
]}
Related cases is a streamfield with blocks inside. Each block holds a reference to another case page. Which is 13 in this case.
I would like to include some fields from this case into the response, like this:
{"related_cases": [
{
"type": "related_case_block",
"value": {
"case": {
"id": 13,
"title": "Case 2"
},
"short_text": "Case 2 is related!"
},
"id": "3aec5efe-55dc-441f-aa5c-fbbb801d237a"
}
]}
Can anyone explain how I would accomplish this?
Assuming you've defined related_case_block as a subclass of StructBlock, you can override the get_api_representation method on that class:
class RelatedCaseBlock(blocks.StructBlock):
# ...
def get_api_representation(self, value, context=None):
return {
'case': {
'id': value['case'].id
'title': value['case'].title
},
'short_text': value['short_text']
}

Indexing unknown nodes in Firebase

My data structure is like this:
firebase-endpoint/updates/<location_id>/<update_id>
each location has many updates that firebase adds as "array" elements.
How can I index on the "validFrom" property of each update if the location_id is unknown before insertion into the databse?
{
"rules": {
"updates": {
"<location_id>": { // WHAT IS THIS NODE SUPPOSED TO BE?
".indexOn": ["validFrom"]
}
}
}
}
data structure sample
{
"71a57e17cbfd0f524680221b9896d88c5ab400b3": {
"-KBHwULMDZ4EL_B48-if": {
"place_id": "71a57e17cbfd0f524680221b9896d88c5ab400b3",
"name": "Gymbox Bank",
"statusValueId": 2,
"update_id": "NOT_SET",
"user_id": "7017a0f5-04a7-498c-9ccd-c547728deffb",
"validFrom": 1456311760554,
"votes": 1
}
},
"d9a02ab407543155d86b84901c69797cb534ac17": {
"-KBHgPkz_buv7DzOFHbD": {
"place_id": "d9a02ab407543155d86b84901c69797cb534ac17",
"name": "The Ivy Chelsea Garden",
"update_id": "NOT_SET",
"user_id": "7017a0f5-04a7-498c-9ccd-c547728deffb",
"validFrom": 1456307547374,
"votes": 0
}
}
}
Update: I don't think this is a dupe of the said question becauase that question doesn't have a parent object with an unknown id as well. ie both <location_id> and <update_id> are free form keys and cannot be set by hand
I did a bit more digging in the docs and I think this should work:
{
"rules": {
"updates": {
"$location_id": { // $location_id should act like a wild card
".indexOn": ["validFrom"]
}
}
}
}