Ignorieren Sie Felder aus dem Java-Objekt dynamisch, während Sie als JSON aus Spring MVC senden

103

Ich habe eine Modellklasse wie diese für den Winterschlaf

@Entity
@Table(name = "user", catalog = "userdb")
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements java.io.Serializable {

    private Integer userId;
    private String userName;
    private String emailId;
    private String encryptedPwd;
    private String createdBy;
    private String updatedBy;

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "UserId", unique = true, nullable = false)
    public Integer getUserId() {
        return this.userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    @Column(name = "UserName", length = 100)
    public String getUserName() {
        return this.userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Column(name = "EmailId", nullable = false, length = 45)
    public String getEmailId() {
        return this.emailId;
    }

    public void setEmailId(String emailId) {
        this.emailId = emailId;
    }

    @Column(name = "EncryptedPwd", length = 100)
    public String getEncryptedPwd() {
        return this.encryptedPwd;
    }

    public void setEncryptedPwd(String encryptedPwd) {
        this.encryptedPwd = encryptedPwd;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    @Column(name = "UpdatedBy", length = 100)
    public String getUpdatedBy() {
        return this.updatedBy;
    }

    public void setUpdatedBy(String updatedBy) {
        this.updatedBy = updatedBy;
    }
}

In Spring MVC Controller kann ich mit DAO das Objekt abrufen. und als JSON-Objekt zurückkehren.

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/getUser/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public User getUser(@PathVariable Integer userId) throws Exception {

        User user = userService.get(userId);
        user.setCreatedBy(null);
        user.setUpdatedBy(null);
        return user;
    }
}

Der Ansichtsteil wird mit AngularJS ausgeführt, sodass JSON wie folgt angezeigt wird

{
  "userId" :2,
  "userName" : "john",
  "emailId" : "[email protected]",
  "encryptedPwd" : "Co7Fwd1fXYk=",
  "createdBy" : null,
  "updatedBy" : null
}

Wenn ich kein verschlüsseltes Passwort festlegen möchte, setze ich dieses Feld auch auf null.

Aber ich möchte nicht so, ich möchte nicht alle Felder an die Client-Seite senden. Wenn ich nicht möchte, dass ein Passwort gesendet, aktualisiert durch, erstellt von Feldern gesendet wird, sollte mein Ergebnis JSON wie folgt sein

{
  "userId" :2,
  "userName" : "john",
  "emailId" : "[email protected]"
}

Die Liste der Felder, die ich nicht an den Client senden möchte, stammt aus einer anderen Datenbanktabelle. Es ändert sich also je nach dem angemeldeten Benutzer. Wie kann ich das tun?

Ich hoffe du hast meine Frage bekommen.

iCode
quelle
Was würden Sie zu dieser Antwort sagen? stackoverflow.com/a/30559076/3488143
Iryna
Diese Informationen können hilfreich sein stackoverflow.com/questions/12505141/…
Musa

Antworten:

141

Fügen Sie die @JsonIgnoreProperties("fieldname")Anmerkung Ihrem POJO hinzu.

Oder Sie können @JsonIgnorevor dem Namen des Felds verwenden, das Sie beim Deserialisieren von JSON ignorieren möchten. Beispiel:

@JsonIgnore
@JsonProperty(value = "user_password")
public String getUserPassword() {
    return userPassword;
}

GitHub Beispiel

user3145373 ツ
quelle
63
Kann ich es dynamisch machen? Nicht in POJO? Kann ich das in meiner Controller-Klasse machen?
iCode
3
@iProgrammer: Hier ist eine ähnliche wie Sie wollen: stackoverflow.com/questions/8179986/…
user3145373 ツ
3
@iProgrammer: sehr beeindruckende Antwort hier stackoverflow.com/questions/13764280/…
user3145373 ツ
11
Bemerkung: @JsonIgnoreist com.fasterxml.jackson.annotation.JsonIgnorenicht org.codehaus.jackson.annotate.JsonIgnore.
Xiaohuo
5
Dies wird sowohl beim Lesen von der Anfrage als auch beim Senden der Antwort ignoriert. Ich möchte nur beim Senden einer Antwort ignorieren, da ich diese Eigenschaft vom Anforderungsobjekt benötige. Irgendwelche Ideen?
Zulkarnain Shah
32

Ich weiß, dass ich ein bisschen zu spät zur Party komme, aber ich bin auch vor ein paar Monaten darauf gestoßen. Alle verfügbaren Lösungen waren für mich nicht sehr ansprechend (Mixins? Ugh!), Daher habe ich eine neue Bibliothek erstellt, um diesen Prozess sauberer zu gestalten. Es ist hier verfügbar, wenn jemand es ausprobieren möchte: https://github.com/monitorjbl/spring-json-view .

Die grundlegende Verwendung ist ziemlich einfach. Sie verwenden das JsonViewObjekt in Ihren Controller-Methoden wie folgt :

import com.monitorjbl.json.JsonView;
import static com.monitorjbl.json.Match.match;

@RequestMapping(method = RequestMethod.GET, value = "/myObject")
@ResponseBody
public void getMyObjects() {
    //get a list of the objects
    List<MyObject> list = myObjectService.list();

    //exclude expensive field
    JsonView.with(list).onClass(MyObject.class, match().exclude("contains"));
}

Sie können es auch außerhalb des Frühlings verwenden:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import static com.monitorjbl.json.Match.match;

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(JsonView.class, new JsonViewSerializer());
mapper.registerModule(module);

mapper.writeValueAsString(JsonView.with(list)
      .onClass(MyObject.class, match()
        .exclude("contains"))
      .onClass(MySmallObject.class, match()
        .exclude("id"));
monitorjbl
quelle
5
Danke dir! Dies war der richtige Weg für mich. Ich brauchte benutzerdefinierte JSON-Ansichten mit denselben Objekten an verschiedenen Orten, und @JsonIgnore würde einfach nicht funktionieren. Diese Bibliothek hat es ganz einfach gemacht, fertig zu werden.
Jeff
2
Sie haben meinen Code sauberer und die Implementierung einfacher gemacht. danke
anindis
@monitorjbl: Dies ist ein bisschen aus der Bahn geraten. Ich habe JSON-Ansichten verwendet und es hat meinen Zweck gelöst. Ich kann jedoch keinen benutzerdefinierten Serializer für die Klasse java.util.Date registrieren (kein Laufzeit- / Kompilierungsfehler), da ich für einen String einen benutzerdefinierten Serializer registrieren konnte.
Ninad
16

Kann ich das dynamisch machen?

Ansichtsklasse erstellen:

public class View {
    static class Public { }
    static class ExtendedPublic extends Public { }
    static class Internal extends ExtendedPublic { }
}

Kommentieren Sie Ihr Modell

@Document
public class User {

    @Id
    @JsonView(View.Public.class)
    private String id;

    @JsonView(View.Internal.class)
    private String email;

    @JsonView(View.Public.class)
    private String name;

    @JsonView(View.Public.class)
    private Instant createdAt = Instant.now();
    // getters/setters
}

Geben Sie die Ansichtsklasse in Ihrem Controller an

@RequestMapping("/user/{email}")
public class UserController {

    private final UserRepository userRepository;

    @Autowired
    UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @RequestMapping(method = RequestMethod.GET)
    @JsonView(View.Internal.class)
    public @ResponseBody Optional<User> get(@PathVariable String email) {
        return userRepository.findByEmail(email);
    }

}

Datenbeispiel:

{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
Hett
quelle
1
Dies ist eine fantastische und minimalistische Antwort! Ich wollte als JSON nur wenige Felder von einer mit @Configuration annotierten Komponente zurückgeben und alle internen Felder überspringen, die automatisch enthalten sind. Vielen Dank!
stx
15

Wir können dies tun, indem wir den Zugriff auf JsonProperty.Access.WRITE_ONLYfestlegen , während wir die Eigenschaft deklarieren.

@JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
@SerializedName("password")
private String password;
Ceekay
quelle
12

Hinzufügen @JsonInclude(JsonInclude.Include.NON_NULL)(zwingt Jackson, Nullwerte zu serialisieren) sowohl zur Klasse als auch @JsonIgnorezum Kennwortfeld.

Sie können natürlich auch @JsonIgnoreauf createdBy und updatedBy setzen, wenn Sie dies immer ignorieren möchten und nicht nur in diesem speziellen Fall.

AKTUALISIEREN

Für den Fall, dass Sie die Anmerkung nicht zum POJO selbst hinzufügen möchten, sind Jacksons Mixin-Anmerkungen eine gute Option. Lesen Sie die Dokumentation

geoand
quelle
Kann ich es dynamisch machen? Nicht in POJO? Kann ich das in meiner Controller-Klasse machen?
iCode
Meinen Sie damit, dass Sie die Anmerkungen nicht zum POJO hinzufügen möchten?
Geo und
Weil ich manchmal alle Felder an die Client-Seite senden möchte. Manchmal wenige Felder. Die Felder, die ich an die Client-Seite senden soll, stammen nur aus der Datenbank in der Controller-Klasse. Danach muss ich nur noch festlegen, welche Felder ignoriert werden sollen.
iCode
12

Ja, Sie können angeben, welche Felder als JSON-Antwort serialisiert und welche ignoriert werden sollen. Dies müssen Sie tun, um Eigenschaften dynamisch ignorieren zu implementieren.

1) Zuerst müssen Sie @JsonFilter aus com.fasterxml.jackson.annotation.JsonFilter zu Ihrer Entitätsklasse als hinzufügen.

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("SomeBeanFilter")
public class SomeBean {

  private String field1;

  private String field2;

  private String field3;

  // getters/setters
}

2) Dann müssen Sie in Ihrem Controller das MappingJacksonValue-Objekt erstellen und Filter darauf setzen, und am Ende müssen Sie dieses Objekt zurückgeben.

import java.util.Arrays;
import java.util.List;

import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

@RestController
public class FilteringController {

  // Here i want to ignore all properties except field1,field2.
  @GetMapping("/ignoreProperties")
  public MappingJacksonValue retrieveSomeBean() {
    SomeBean someBean = new SomeBean("value1", "value2", "value3");

    SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");

    FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);

    MappingJacksonValue mapping = new MappingJacksonValue(someBean);

    mapping.setFilters(filters);

    return mapping;
  }
}

Folgendes erhalten Sie als Antwort:

{
  field1:"value1",
  field2:"value2"
}

an Stelle von:

{
  field1:"value1",
  field2:"value2",
  field3:"value3"
}

Hier sehen Sie, dass andere Eigenschaften (in diesem Fall Feld3) als Antwort ignoriert werden, mit Ausnahme der Eigenschaften Feld1 und Feld2.

Hoffe das hilft.

Shafqat Shafi
quelle
1
@Shafqat Mann, vielen Dank, du bist mein Retter. Verbrachte fast einen Tag damit, diese Art von Funktionalität herauszufinden. Diese Lösung ist so elegant und einfach? und macht genau das, was angefordert wurde. Sollte als die richtige Antwort markiert werden.
Oleg Kuts
6

Wenn ich Sie wäre und dies tun wollte, würde ich meine Benutzerentität nicht in der Controller-Schicht verwenden. Stattdessen erstelle ich UserDto (Datenübertragungsobjekt) und verwende es, um mit der Geschäftsschicht (Service) und dem Controller zu kommunizieren. Sie können Apache BeanUtils (copyProperties-Methode) verwenden, um Daten von der Benutzerentität nach UserDto zu kopieren.

Hamedz
quelle
3

Ich habe ein JsonUtil erstellt, mit dem Felder zur Laufzeit ignoriert werden können, während eine Antwort gegeben wird.

Beispielverwendung: Das erste Argument sollte eine beliebige POJO-Klasse (Student) sein, und ignoreFields ist ein durch Kommas getrenntes Feld, das Sie als Antwort ignorieren möchten.

 Student st = new Student();
 createJsonIgnoreFields(st,"firstname,age");

import java.util.logging.Logger;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;

public class JsonUtil {

  public static String createJsonIgnoreFields(Object object, String ignoreFields) {
     try {
         ObjectMapper mapper = new ObjectMapper();
         mapper.getSerializationConfig().addMixInAnnotations(Object.class, JsonPropertyFilterMixIn.class);
         String[] ignoreFieldsArray = ignoreFields.split(",");
         FilterProvider filters = new SimpleFilterProvider()
             .addFilter("filter properties by field names",
                 SimpleBeanPropertyFilter.serializeAllExcept(ignoreFieldsArray));
         ObjectWriter writer = mapper.writer().withFilters(filters);
         return writer.writeValueAsString(object);
     } catch (Exception e) {
         //handle exception here
     }
     return "";
   }

   public static String createJson(Object object) {
        try {
         ObjectMapper mapper = new ObjectMapper();
         ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
         return writer.writeValueAsString(object);
        }catch (Exception e) {
         //handle exception here
        }
        return "";
   }
 }    
Devendra Dora
quelle
2

Ich habe nur mit gelöst, @JsonIgnorewie @kryger vorgeschlagen hat. So wird Ihr Getter:

@JsonIgnore
public String getEncryptedPwd() {
    return this.encryptedPwd;
}

Sie können @JsonIgnorenatürlich wie hier beschrieben auf Feld, Setter oder Getter einstellen .

Wenn Sie das verschlüsselte Kennwort nur auf der Serialisierungsseite schützen möchten (z. B. wenn Sie Ihre Benutzer anmelden müssen), fügen Sie diese @JsonPropertyAnmerkung zu Ihrem Feld hinzu :

@JsonProperty(access = Access.WRITE_ONLY)
private String encryptedPwd;

Mehr Infos hier .

Foxbit
quelle
1

Ich habe mit Spring und Jackson eine Lösung für mich gefunden

Geben Sie zuerst den Filternamen in der Entität an

@Entity
@Table(name = "SECTEUR")
@JsonFilter(ModelJsonFilters.SECTEUR_FILTER)
public class Secteur implements Serializable {

/** Serial UID */
private static final long serialVersionUID = 5697181222899184767L;

/**
 * Unique ID
 */
@Id
@JsonView(View.SecteurWithoutChildrens.class)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@JsonView(View.SecteurWithoutChildrens.class)
@Column(name = "code", nullable = false, length = 35)
private String code;

/**
 * Identifiant du secteur parent
 */
@JsonView(View.SecteurWithoutChildrens.class)
@Column(name = "id_parent")
private Long idParent;

@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "id_parent")
private List<Secteur> secteursEnfants = new ArrayList<>(0);

}

Anschließend können Sie die Konstantenfilter-Namensklasse mit dem StandardfilterProvider anzeigen, der in der Frühlingskonfiguration verwendet wird

public class ModelJsonFilters {

public final static String SECTEUR_FILTER = "SecteurFilter";
public final static String APPLICATION_FILTER = "ApplicationFilter";
public final static String SERVICE_FILTER = "ServiceFilter";
public final static String UTILISATEUR_FILTER = "UtilisateurFilter";

public static SimpleFilterProvider getDefaultFilters() {
    SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAll();
    return new SimpleFilterProvider().setDefaultFilter(theFilter);
}

}

Federkonfiguration:

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "fr.sodebo")

public class ApiRootConfiguration extends WebMvcConfigurerAdapter {

@Autowired
private EntityManagerFactory entityManagerFactory;


/**
 * config qui permet d'éviter les "Lazy loading Error" au moment de la
 * conversion json par jackson pour les retours des services REST<br>
 * on permet à jackson d'acceder à sessionFactory pour charger ce dont il a
 * besoin
 */
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

    super.configureMessageConverters(converters);
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    ObjectMapper mapper = new ObjectMapper();

    // config d'hibernate pour la conversion json
    mapper.registerModule(getConfiguredHibernateModule());//

    // inscrit les filtres json
    subscribeFiltersInMapper(mapper);

    // config du comportement de json views
    mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);

    converter.setObjectMapper(mapper);
    converters.add(converter);
}

/**
 * config d'hibernate pour la conversion json
 * 
 * @return Hibernate5Module
 */
private Hibernate5Module getConfiguredHibernateModule() {
    SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
    Hibernate5Module module = new Hibernate5Module(sessionFactory);
    module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);

    return module;

}

/**
 * inscrit les filtres json
 * 
 * @param mapper
 */
private void subscribeFiltersInMapper(ObjectMapper mapper) {

    mapper.setFilterProvider(ModelJsonFilters.getDefaultFilters());

}

}

Schließlich kann ich einen bestimmten Filter in restConstoller angeben, wenn ich brauche ....

@RequestMapping(value = "/{id}/droits/", method = RequestMethod.GET)
public MappingJacksonValue getListDroits(@PathVariable long id) {

    LOGGER.debug("Get all droits of user with id {}", id);

    List<Droit> droits = utilisateurService.findDroitsDeUtilisateur(id);

    MappingJacksonValue value;

    UtilisateurWithSecteurs utilisateurWithSecteurs = droitsUtilisateur.fillLists(droits).get(id);

    value = new MappingJacksonValue(utilisateurWithSecteurs);

    FilterProvider filters = ModelJsonFilters.getDefaultFilters().addFilter(ModelJsonFilters.SECTEUR_FILTER, SimpleBeanPropertyFilter.serializeAllExcept("secteursEnfants")).addFilter(ModelJsonFilters.APPLICATION_FILTER,
            SimpleBeanPropertyFilter.serializeAllExcept("services"));

    value.setFilters(filters);
    return value;

}
C2dric
quelle
5
warum so Komplikationen für eine einfache Frage
Humoyun Ahmad
1

Platzieren Sie es @JsonIgnoreauf dem Feld oder seinem Getter oder erstellen Sie ein benutzerdefiniertes dto

@JsonIgnore
private String encryptedPwd;

oder wie oben erwähnt, indem Sie ceekayes mit @JsonPropertydem Hinweis versehen, dass das Zugriffsattribut nur zum Schreiben festgelegt ist

@JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
private String encryptedPwd;
Alan Sereb
quelle
0

Würde keine erstellen UserJsonResponse sauberer Klasse zu und mit den gewünschten Feldern zu füllen?

Die direkte Rückgabe eines JSON scheint eine gute Lösung zu sein, wenn Sie das gesamte Modell zurückgeben möchten. Sonst wird es nur chaotisch.

In Zukunft möchten Sie beispielsweise möglicherweise ein JSON-Feld haben, das keinem Modellfeld entspricht, und dann haben Sie größere Probleme.

Leonardo Beal
quelle
0

Dies ist ein sauberes Hilfsprogramm für die obige Antwort :

@GetMapping(value = "/my-url")
public @ResponseBody
MappingJacksonValue getMyBean() {
    List<MyBean> myBeans = Service.findAll();
    MappingJacksonValue mappingValue = MappingFilterUtils.applyFilter(myBeans, MappingFilterUtils.JsonFilterMode.EXCLUDE_FIELD_MODE, "MyFilterName", "myBiggerObject.mySmallerObject.mySmallestObject");
    return mappingValue;
}

//AND THE UTILITY CLASS
public class MappingFilterUtils {

    public enum JsonFilterMode {
        INCLUDE_FIELD_MODE, EXCLUDE_FIELD_MODE
    }
    public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final String... fields) {
        if (fields == null || fields.length == 0) {
            throw new IllegalArgumentException("You should pass at least one field");
        }
        return applyFilter(object, mode, filterName, new HashSet<>(Arrays.asList(fields)));
    }

    public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final Set<String> fields) {
        if (fields == null || fields.isEmpty()) {
            throw new IllegalArgumentException("You should pass at least one field");
        }

        SimpleBeanPropertyFilter filter = null;
        switch (mode) {
            case EXCLUDE_FIELD_MODE:
                filter = SimpleBeanPropertyFilter.serializeAllExcept(fields);
                break;
            case INCLUDE_FIELD_MODE:
                filter = SimpleBeanPropertyFilter.filterOutAllExcept(fields);
                break;
        }

        FilterProvider filters = new SimpleFilterProvider().addFilter(filterName, filter);
        MappingJacksonValue mapping = new MappingJacksonValue(object);
        mapping.setFilters(filters);
        return mapping;
    }
}
Mehdi
quelle
-5

Fügen Sie in Ihrer Entitätsklasse hinzu @JsonInclude(JsonInclude.Include.NON_NULL) Anmerkungen hinzu, um das Problem zu beheben

es wird so aussehen

@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)
Jeevan Gowda
quelle
Völlig irrelevant beantwortet. Der Zweck der Frage ist unterschiedlich, während sich die Antwort auf etwas anderes bezieht. -1 dafür
Hammad Dar