How to apply a JsonView to a nested entity - json-view

I have the following JsonViews defined:
public class EntityJsonView {
public static class Detailed extends Abbreviated {
}
public static class AuditedDetailed extends Detailed {
}
public static class Abbreviated {
}
}
Then I have these classes:
public Class Customer {
#JsonView(EntityJsonView.Abbreviated.class)
private Integer id;
#JsonView(EntityJsonView.Abbreviated.class)
private String name;
#JsonView(EntityJsonView.Detailed.class)
private String phone;
#JsonView(EntityJsonView.Detailed.class)
private List<Invoice> invoices;
}
public Class Invoice {
#JsonView(EntityJsonView.Abbreviated.class)
private Integer id;
#JsonView(EntityJsonView.Detailed.class)
private Customer customer;
#JsonView(EntityJsonView.Detailed.class)
private Employee salesman;
#JsonView(EntityJsonView.Abbreviated.class)
private Date invoiceDate;
#JsonView(EntityJsonView.Abbreviated.class)
private Double amount;
}
I return my customer list like this:
#JsonView(EntityJsonView.Detailed.class)
public ResponseEntity<List<Customer>> getCustomerList() {
List<Customer> custs = customerService.getAll();
return new ResponseEntity<List<Customer>>(custs , HttpStatus.OK);
}
While I want the Customer instances to be serialized using the Detailed view, I want the nested Invoice instances to be serialized using the Abbreviated view. By the same token, when I serialize a list of Invoices using the Detailed view, I want the nested Customer instances to be serialized using the Abbreviated view. This is not just a problem of recursion because there are lots of other attributes I want to remove as well.
I've searched high and low for a solution but perhaps I'm not using the right keywords.
My predecessor in this job accomplished this using #JsonIgnoreProperties but that is proving to be a maintenance problem. When a new attribute is added to a class, I have to hunt down all the ignore lists and decide if it needs to be ignored or not. It would be easier if there was a corresponding #JsonIncludeProperties.
Has anyone found a better way to accomplish this?

I figured out a way to sort of do this and it works for my environment. I'm posting in case someone else has a similar issue. The first step is to create a view for each of your top-level entities. In this example, those will be Foo, Bar, and Snafu. These should all inherit from an abbreviated view.
public class EntityViews {
public static interface Abbr {}
public static interface Foo extends Abbr {}
public static interface Bar extends Abbr {}
public static interface Snafu extends Abbr {}
public static interface Detailed extends Foo, Bar, Snafu {}
}
I used interface because it allows multiple inheritance. All the main class views end up in the Detailed view. Now for the classes:
#JsonView(EntityViews.Foo.class)
public class Foo {
#JsonView(EntityViews.Abbr.Class)
private Integer id;
#JsonView(EntityViews.Abbr.Class)
private String name;
private String description;
private Bar bar;
}
#JsonView(EntityViews.Bar.class)
public class Bar {
#JsonView(EntityViews.Abbr.Class)
private Integer id;
#JsonView(EntityViews.Abbr.Class)
private String name;
private List<Snafu> snafus;
}
#JsonView(EntityViews.Snafu.class)
public class Snafu {
#JsonView(EntityViews.Abbr.Class)
private Integer id;
#JsonView(EntityViews.Abbr.Class)
private String name;
#JsonView(EntityViews.Bar.class, EntityViews.Snafu.class)
#JsonIgnoreProperties("parent", "children")
private Snafu parent;
#JsonIgnoreProperties("parent", "children")
private List<Snafu> children;
}
Now, let's do the endpoints:
#RestController
#RequestMapping("/api/foos")
#CrossOrigin
public class FooController {
#JsonView(EntityViews.Foo.class)
#GetMapping("/")
public ResponseEntity<List<Foo>> get() {
List<Foo> list = service.getAll();
return new ResponseEntity<List<Foo>>(list, HttpStatus.OK);
}
}
#RestController
#RequestMapping("/api/bars")
#CrossOrigin
public class BarController {
#JsonView(EntityViews.Bar.class)
#GetMapping("/")
public ResponseEntity<List<Foo>> get() {
List<Bar> list = service.getAll();
return new ResponseEntity<List<Bar>>(list, HttpStatus.OK);
}
}
#RestController
#RequestMapping("/api/snafus")
#CrossOrigin
public class SnafuController {
#JsonView(EntityViews.Snafu.class)
#GetMapping("/")
public ResponseEntity<List<Snafu>> get() {
List<Snafu> list = service.getAll();
return new ResponseEntity<List<Snafu>>(list, HttpStatus.OK);
}
}
So as we see, each controller assigns the view corresponding to the entity that is being returned. Since those views all
inherit from Abbr, all other entities being returned will have the Abbr view applied to them.
Notice in the Snafu class that the parent attribute is assigned to both the Bar and Snafu views. So when you return a Bar endpoint,
you will get that attribute as well as the Abbr attributes (I haven't tested this so YMMV. Will edit if it doesn't work like I think it will).
The one place this strategy breaks down is if you have attributes that are the same class as the entity. In that case, you will still
have to use #JsonIgnoreProperties to control what is returned but that is a small price to pay for not having to have those on virtually
every entity attribute.

Related

How to change property values in a IList<T>

Public class temp()
{
int code;
string name;
}
IList<temp> res="assume is has a list of values"
public void modify<T>(ref Ilist<T> list)
{
list[0].code=0;
list[0].name="";
}
i was getting an error "list does not contain a definition for code/name" . is there any way to change the values in IList
You current example does not makes much sense.
But one way is to constraint your method's generic type parameter to some interface like:
public interface ITemp
{
int code { get; set; }
string name {get; set;}
}
and your specific types would be inheriting it with some implementation
public class Temp : ITemp
{
// implementation
}
and then you can write a generic method :
public void modify<T>(ref IList<T> list) where T : ITemp
{
list[0].code=0;
list[0].name="";
}
But this should be done only when you have multiple classes with some common properties and you need a generic method to do some work on those.

ComboBox and original value as an array

I have a legacy MongoDB database (collection) where there is single value stored as an array List . But its only a one value that must be, in UI selected with ComboBox. So I have a model bean
class Project {
List<Company> companies;
}
And I would like to bind it and edit with VAADIN ComboBox. Initialy I thought I can use some customer converter for ComboBox but can't get it to work. Combo should edit the first value in the List (Company bean) and store back into companies field as an array to remain compatible. Is it event possible to do it and if so can you give me some hint how to accomplish this?
EDIT: Enhanced explanation
MongoDB model:
class Project {
List<Company> companies;
}
Vaadin UI:
ComboBox companies;
... 'companies' ComboBox is attached to BeanItemContainer which is List ... selection is therefore the only one Company bean, but should be stored, for compatibility reasons, as List with only one item. So basicly ComboBox should be able to read existing List value as single Company, allow selection and store it as List with this one selecten Company.
After the last question update the answer below may not be correct. Waiting for OP to provide details so the answer can be corrected.
You can use a sort of a delegation approach using a ProjectDelegator wrapper. This will allow you to bind the form to the Project name (using the project field) as well as the Company name (using the getCompany() getter)
1. The UI class
#PreserveOnRefresh
#SpringUI
public class MyVaadinUI extends UI {
#Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
// add the form to the UI
layout.addComponent(new MyForm(new ProjectDelegator(new Project("myProject", new Company("myCompany")))));
}
}
2. The form
// "wrapping" the form in a custom component. not really needed but a nice touch
public class MyForm extends CustomComponent {
public MyForm(ProjectDelegator projectDelegator) {
FormLayout layout = new FormLayout();
// use a binder to create fields and bind the members using reflection
BeanFieldGroup<ProjectDelegator> binder = new BeanFieldGroup<>(ProjectDelegator.class);
// bind the project name using the "project" field
layout.addComponent(binder.buildAndBind("Project", "project.name"));
// bind the company name using the "getCompany" method
layout.addComponent(binder.buildAndBind("Company", "company.name"));
// add a "save" button
layout.addComponent(new Button("Save", new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent clickEvent) {
try {
// commit changes
binder.commit();
} catch (FieldGroup.CommitException e) {
// didn't expect this! what gives?!
System.out.println("Could not save data: [" + e.getMessage() + "]");
}
}
}));
// set the delegator as the binder data source
binder.setItemDataSource(projectDelegator);
setCompositionRoot(layout);
}
}
3. The "delegator"
// delegator class to adapt from/to list with only 1 item
public class ProjectDelegator {
// nice constant to express the intent as clearly as possible
private static final int MY_ONLY_COMPANY = 0;
// our delegate
private Project project;
public ProjectDelegator(Project project) {
this.project = project;
}
// BeanFieldGroup will use this by reflection to bind the "company.name" field
public Company getCompany() {
// delegate the accessing to the origina product
return project.getCompanies().get(MY_ONLY_COMPANY);
}
// accessor; can if not required at a later time
public Project getProject() {
return project;
}
}
4. The legacy model classes
// our problem class
public class Project {
private String name;
private List<Company> companies = new ArrayList<>();
public Project(String name, Company company) {
this.name = name;
companies.add(company);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Company> getCompanies() {
return companies;
}
public void setCompanies(List<Company> companies) {
this.companies = companies;
}
}
// the "indirect" problem :)
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Setting actual company name to [" + name + "]");
this.name = name;
}
}
5. Resulting UI
6. Pressing the save button
Setting actual company name to [myNewCompany]

Web API Error: The 'ObjectContent`1' type failed to serialize the response body for content type

I am getting this error when attempting to use a Web API controller.
Web API Error: The 'ObjectContent`1' type failed to serialize the response body for content type
the code in my controller is as follows
public IEnumerable<Student> GetAllStudents()
{
var allstudents = unitOfWork.StudentRepository.Get(includeProperties: "Groups");
return allstudents;
}
public Student GetStudentByID(Guid id)
{
return unitOfWork.StudentRepository.GetByID(id);
}
and my 'Student' class is as follows
public partial class Student
{
public Student()
{
this.Groups = new HashSet<Group>();
}
public System.Guid StudentID { get; set; }
public string Surname { get; set; }
public string FirstName { get; set; }
public byte[] Timestamp { get; set; }
public virtual Course Course { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
Both methods result in the same error.
My inner exception is as follows
Type
'System.Data.Entity.DynamicProxies.Student_4C97D068E1AD0BA62C3C6E441601FFB7418AD2D635F7F1C14B64F4B2BE32DF9A'
with data contract name
'Student_4C97D068E1AD0BA62C3C6E441601FFB7418AD2D635F7F1C14B64F4B2BE32DF9A:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the
list of known types passed to DataContractSerializer.
I have a feeling I need to use the KnownType attribute but I'm not exactly sure how to implement it.
Any help would be appreciated
If you don't need the lazy-loaded navigation properties provided by the proxy class (System.Data.Entity.DynamicProxies.Student_4C97D068E1A...), you can disable their generation by setting:
unitOfWork.Configuration.ProxyCreationEnabled = false;
What to do if you need the proxy class is another question.
Follow these links for a good overview of lazy loading and proxies:
Loading Related Entities
Working with Proxies
Should I enable or disable dynamic proxies
I usually disable lazy loading and proxies by default, and enable one or both in specific code blocks that need them.
What is the inner exception message? The inner exception message will be the actual exception that is thrown by the serializer and it should tell us which type is causing the exception.
Let me guess -- Is it any the type Course and the type Group? If so, try putting KnownType attribute on the actual implementation type of your class Student
[KnownType(typeof(GroupA))]
[KnownType(typeof(CourseA))]
public partial class Student
{...}
public class GroupA : Group {...}
public class CourseA : Course {...}
public interface Group {...}
public interface Course {...}

Why is my full view model not being serialized by ApiController?

I have the following type hierarchy for ClientIndexModel:
public class ViewModel
{
public virtual IDictionary<string, SelectList> SelectListDictionary
{
get
{
var props = GetType().GetProperties().Where(p => p.PropertyType == typeof(SelectList));
return props.ToDictionary(prop => prop.Name, prop => (SelectList)prop.GetValue(this, null));
}
}
}
public class IndexModel<TIndexItem, TEntity> : ViewModel where TIndexItem : ViewModel where TEntity : new()
{
public List<TIndexItem> Items { get; private set; }
}
public class ClientIndexModel: IndexModel<ClientIndexItem, Client>
{
}
I instantiate in and return a ClientIndexModel from an ApiController as follows:
public ClientIndexModel Get()
{
var model = new ClientIndexModel();
return model;
}
If I inspect model with a breakpoint on the return model; line, the Items property is present, with a count of 0. Yet the JSON returned from this action only has the SelectListDictionary property and no Items property. Why could this be?
Your Items property has a private setter. Properties with private setters are intentionally omitted from serialization as it makes no sense to serialize them because they can never be deserialized back as their values cannot be modified from the outside. So you should either completely remove the setter (as you have done for the SelectListDictionary property), make it public or write a custom formatter using some custom serializer that is capable of serializing properties with private setters.

Composing polymorphic objects in ASP.NET MVC3 project

The essence of my question is how to compose these objects (see below) in a sensible way with MVC3 and Ninject (though I am not sure DI should be playing a role in the solution). I can't disclose the real details of my project but here is an approximation which illustrates the issue/question. Answers in either VB or C# are appreciated!
I have several different products with widely varying properties yet all of them need to be represented in a catalog. Each product class has a corresponding table in my database. A catalog entry has a handful of properties specific to being a catalog entry and consequently have their own table. I have defined an interface for the catalog entries with the intent that calling the DescriptionText property will give me very different results based on the underlying concrete type.
Public Class Clothing
Property Identity as Int64
Property AvailableSizes As List(Of String)
Property AvailableColor As List(Of String)
End Class
Public Class Fasteners
Property Identity as Int64
Property AvailableSizes As List(Of String)
Property AvailableFinishes As List(Of String)
Property IsMetric As Boolean
End Class
Public Interface ICatalogEntry
Property ProductId as Int64
Property PublishedOn As DateTime
Property DescriptionText As String
End Interface
Given that the DescriptionText is a presentation layer concern I don't want to implement the ICatalogEntry interface in my product classes. Instead I want to delegate that to some kind of formatter.
Public Interface ICatalogEntryFormatter
Property DescriptionText As String
End Interface
Public Class ClothingCatalogEntryFormatter
Implements ICatalogEntryFormatter
Property DescriptionText As String
End Class
Public Class FastenerCatalogEntryFormatter
Implements ICatalogEntryFormatter
Property DescriptionText As String
End Class
In a controller somewhere there will be code like this:
Dim entries As List(Of ICatalogEntry)
= catalogService.CurrentCatalog(DateTime.Now)
In a view somewhere there will be code like this:
<ul>
#For Each entry As ICatalogEntry In Model.Catalog
#<li>#entry.DescriptionText</li>
Next
</ul>
So the question is what do the constructors look like? How to set it up so the appropriate objects are instantiated in the right places. Seems like generics or maybe DI can help with this but I seem to be having a mental block. The only idea I've come up with is to add a ProductType property to ICatalogEntry and then implement a factory like this:
Public Class CatalogEntryFactory
Public Function Create(catEntry as ICatalogEntry) As ICatalogEntry
Select Case catEntry.ProductType
Case "Clothing"
Dim clothingProduct = clothingService.Get(catEntry.ProductId)
Dim clothingEntry = New ClothingCatalogEntry(clothingProduct)
Return result
Case "Fastener"
Dim fastenerProduct = fastenerService.Get(catEntry.ProductId)
Dim fastenerEntry = New FastenerCatalogEntry(fastenerProduct)
fastenerEntry.Formatter = New FastenerCatalogEntryFormatter
Return fastenerEntry
...
End Function
End Class
Public ClothingCatalogEntry
Public Sub New (product As ClothingProduct)
Me.Formatter = New ClothingCatalogEntryFormatter(product)
End Sub
Property DescriptionText As String
Get
Return Me.Formatter.DescriptionText
End Get
End Property
End Class
...FastenerCatalogEntry is omitted but you get the idea...
Public Class CatalogService
Public Function CurrentCatalog(currentDate as DateTime)
Dim theCatalog As List(Of ICatalogEntry)
= Me.repository.GetCatalog(currentDate)
Dim theResult As New List(Of ICatalogEntry)
For Each entry As ICataLogEntry In theCatalog
theResult.Add(factory.Create(entry))
Next
Return theResult
End Function
End Class
IMHO, I am not really getting any smells off this code other than having to change the factory for every new product class that comes along. Yet, my gut says that this is the old way of doing things and nowadays DI and/or generics can do this better. Suggestions on how to handle this are much appreciated (as are suggestions on a better title...)
I like to just use the default constructor on models for the view and populate them via Automapper.
I would have a view model like this:
public interface IHasDescription
{
public string DescriptionText { get; set; }
}
public class ViewModelType : IHasDescription
{
[DisplayName("This will be rendered in the view")]
public string SomeText { get; set; }
public string DescriptionText { get; set; }
}
And I have a model from the DAL like this:
public class DALModelType
{
public string SomeText { get; set; }
}
So you have something like this in your controller:
var dalModel = someRepository.GetAll();
var viewModel = Mapper.Map<DALModelType, ViewModelType>(dalModel);
And you have the Automapper setup code in some file. This way you only have the conversion code in one place instead of in multiple methods/controllers. You have a custom resolver which uses dependency injection (instead of () => new CustomResolver()) and this will house your logic for getting the display text.
Mapper.CreateMap<IHasDescription, ViewModelType>()
.ForMember(dest => dest.DescriptionText,
opt => opt.ResolveUsing<CustomResolver>().ConstructedBy(() => new CustomResolver()));
Not sure if this works with your workflow but it should be able to get you what you want.
So making a few small changes I got this to work using the Ninject Factory extension.
Biggest change is that my entities have enough info to display either type (clothes or fasteners in my contrived example) if the item is actually clothes then the fastener specific properties will be null and vice versa.
Public Interface IDescribable
ReadOnly Property DescriptionText As String
End Interface
Public Enum ProductType
CLOTHING
FASTENER
End Enum
Public Interface ICatalogEntry
Inherits IDescribable
ReadOnly Property ProductId As Int64
ReadOnly Property PublishedOn As DateTime
ReadOnly Property ProductType As ProductType
End Interface
Public Class CatalogEntryEntity
Public Property ProductId As Long
Public Property ProductType As ProductType
Public Property PublishedOn As Date
Public Property DescriptionText As String
Public Property Color As String
Public Property Finish As String
Public Property IsMetric As Boolean
End Class
Then with this in place I can define my catalog service as follows:
Public Class CatalogService
Private ReadOnly _factory As ICatalogEntryFactory
Private ReadOnly _repository As CatalogRepository
Public Sub New(entryFactory As ICatalogEntryFactory, repository As CatalogRepository)
Me._factory = entryFactory
Me._repository = repository
End Sub
Public Function CurrentCatalog(currentDate As DateTime) As List(Of ICatalogEntry)
Dim items = Me._repository.GetCatalog()
Return (From item In items Select _factory.Create(item.ProductType.ToString(), item)).ToList()
End Function
End Class
Public Interface ICatalogEntryFactory
Function Create(bindingName As String, entity As CatalogEntryEntity) As ICatalogEntry
End Interface
Ninject will provide the factory (which is awesome!) assuming I setup the bindings like this:
theKernel.Bind(Of ICatalogEntry)().To(Of ClothingCatalogEntry)().Named("CLOTHING")
theKernel.Bind(Of ICatalogEntry)().To(Of FastenerCatalogEntry)().Named("FASTENER")
theKernel.Bind(Of ICatalogEntryFactory)().ToFactory(Function() New UseFirstParameterAsNameInstanceProvider())
I've omitted the FastenerCatalogEntry for brevity; the ClothingCatalogEntry is like this:
Public Class ClothingCatalogEntry
Public Sub New(ByVal entity As CatalogEntryEntity)
...
It was this post that helped me the most to figure this out. I used UseFirstParameterAsNameInstanceProvider exactly as shown there.

Resources