DATAREST-238 - Changed JSON deserializer to ignore links.
Links are a read-only and basically have to be ignored during any inputs. This required removing the deserializer from PersistentEntityJackson2Module. This will cause the object mapping to fail as it doesn't know about the links property in the request body. Instead of requiring that every entity apply the necessary annotation, the mapper is configured to not fail on non-existent attributes. This allowed all the tests to pass while properly handling PUT operations. Original pull request: #130.
This commit is contained in:
committed by
Oliver Gierke
parent
5dd33ba7c0
commit
ef1ee10940
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -64,6 +64,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@RepositoryRestController
|
||||
class RepositoryEntityController extends AbstractRepositoryRestController implements ApplicationEventPublisherAware {
|
||||
@@ -237,10 +238,14 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
Object obj = invoker.invokeSave(domainObj);
|
||||
publisher.publishEvent(new AfterSaveEvent(obj));
|
||||
|
||||
Link selfLink = perAssembler.getSelfLinkFor(obj);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setLocation(URI.create(selfLink.getHref()));
|
||||
|
||||
if (config.isReturnBodyOnUpdate()) {
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, null, perAssembler.toResource(obj));
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, perAssembler.toResource(obj));
|
||||
} else {
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.NO_CONTENT, null, null);
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.NO_CONTENT, headers, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
@@ -482,6 +483,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
// Our special PersistentEntityResource Module
|
||||
objectMapper.registerModule(persistentEntityJackson2Module());
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
|
||||
// Configure custom Modules
|
||||
configureJacksonObjectMapper(objectMapper);
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.json;
|
||||
|
||||
import static org.springframework.beans.BeanUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
@@ -24,7 +32,6 @@ import org.springframework.data.mapping.SimpleAssociationHandler;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.UriDomainClassConverter;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
@@ -33,38 +40,31 @@ import org.springframework.data.rest.webmvc.PersistentEntityResource;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.core.JsonGenerationException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class PersistentEntityJackson2Module extends SimpleModule implements InitializingBean {
|
||||
public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = -7289265674870906323L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersistentEntityJackson2Module.class);
|
||||
private static final TypeDescriptor URI_TYPE = TypeDescriptor.valueOf(URI.class);
|
||||
|
||||
private final ResourceMappings mappings;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
@Autowired private Repositories repositories;
|
||||
@Autowired private RepositoryRestConfiguration config;
|
||||
@Autowired private UriDomainClassConverter uriDomainClassConverter;
|
||||
|
||||
public PersistentEntityJackson2Module(ResourceMappings resourceMappings, ConversionService conversionService) {
|
||||
|
||||
@@ -74,7 +74,6 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
this.mappings = resourceMappings;
|
||||
this.conversionService = conversionService;
|
||||
|
||||
addSerializer(new ResourceSerializer());
|
||||
}
|
||||
@@ -101,142 +100,6 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
for (Class<?> domainType : repositories) {
|
||||
PersistentEntity<?, ?> pe = repositories.getPersistentEntity(domainType);
|
||||
if (null == pe) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("The domain class {} does not have PersistentEntity metadata.", domainType.getName());
|
||||
}
|
||||
} else {
|
||||
addDeserializer(domainType, new ResourceDeserializer(pe));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ResourceDeserializer<T extends Object> extends StdDeserializer<T> {
|
||||
|
||||
private static final long serialVersionUID = 8195592798684027681L;
|
||||
private final PersistentEntity<?, ?> persistentEntity;
|
||||
|
||||
private ResourceDeserializer(final PersistentEntity<?, ?> persistentEntity) {
|
||||
super(persistentEntity.getType());
|
||||
this.persistentEntity = persistentEntity;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "incomplete-switch", "unused" })
|
||||
@Override
|
||||
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
Object entity = instantiateClass(handledType());
|
||||
|
||||
BeanWrapper<?, Object> wrapper = BeanWrapper.create(entity, conversionService);
|
||||
ResourceMetadata metadata = mappings.getMappingFor(handledType());
|
||||
|
||||
for (JsonToken tok = jp.nextToken(); tok != JsonToken.END_OBJECT; tok = jp.nextToken()) {
|
||||
String name = jp.getCurrentName();
|
||||
switch (tok) {
|
||||
case FIELD_NAME: {
|
||||
if ("href".equals(name)) {
|
||||
URI uri = URI.create(jp.nextTextValue());
|
||||
TypeDescriptor entityType = TypeDescriptor.forObject(entity);
|
||||
if (uriDomainClassConverter.matches(URI_TYPE, entityType)) {
|
||||
entity = uriDomainClassConverter.convert(uri, URI_TYPE, entityType);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ("rel".equals(name)) {
|
||||
// rel is currently ignored
|
||||
continue;
|
||||
}
|
||||
|
||||
PersistentProperty<?> persistentProperty = persistentEntity.getPersistentProperty(name);
|
||||
if (null == persistentProperty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object val = null;
|
||||
|
||||
if ("links".equals(name)) {
|
||||
if ((tok = jp.nextToken()) == JsonToken.START_ARRAY) {
|
||||
while ((tok = jp.nextToken()) != JsonToken.END_ARRAY) {
|
||||
// Advance past the links
|
||||
}
|
||||
} else if (tok == JsonToken.VALUE_NULL) {
|
||||
// skip null value
|
||||
} else {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"Property 'links' is not of array type. Either eliminate this property from the document or make it an array.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null == persistentProperty) {
|
||||
// do nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try and read the value of this attribute.
|
||||
// The method of doing that varies based on the type of the property.
|
||||
if (persistentProperty.isCollectionLike()) {
|
||||
|
||||
Class<? extends Collection<?>> collectionType = (Class<? extends Collection<?>>) persistentProperty
|
||||
.getType();
|
||||
Collection<Object> collection = CollectionFactory.createCollection(collectionType, 0);
|
||||
|
||||
if ((tok = jp.nextToken()) == JsonToken.START_ARRAY) {
|
||||
while ((tok = jp.nextToken()) != JsonToken.END_ARRAY) {
|
||||
Object cval = jp.readValueAs(persistentProperty.getComponentType());
|
||||
collection.add(cval);
|
||||
}
|
||||
|
||||
val = collection;
|
||||
} else if (tok == JsonToken.VALUE_NULL) {
|
||||
val = null;
|
||||
} else {
|
||||
throw new HttpMessageNotReadableException("Cannot read a JSON " + tok + " as a Collection.");
|
||||
}
|
||||
} else if (persistentProperty.isMap()) {
|
||||
|
||||
Class<? extends Map<?, ?>> mapType = (Class<? extends Map<?, ?>>) persistentProperty.getType();
|
||||
Map<Object, Object> map = CollectionFactory.createMap(mapType, 0);
|
||||
|
||||
if ((tok = jp.nextToken()) == JsonToken.START_OBJECT) {
|
||||
do {
|
||||
name = jp.getCurrentName();
|
||||
// TODO resolve domain object from URI
|
||||
tok = jp.nextToken();
|
||||
Object mval = jp.readValueAs(persistentProperty.getMapValueType());
|
||||
|
||||
map.put(name, mval);
|
||||
} while ((tok = jp.nextToken()) != JsonToken.END_OBJECT);
|
||||
|
||||
val = map;
|
||||
} else if (tok == JsonToken.VALUE_NULL) {
|
||||
val = null;
|
||||
} else {
|
||||
throw new HttpMessageNotReadableException("Cannot read a JSON " + tok + " as a Map.");
|
||||
}
|
||||
} else {
|
||||
if ((tok = jp.nextToken()) != JsonToken.VALUE_NULL) {
|
||||
val = jp.readValueAs(persistentProperty.getType());
|
||||
}
|
||||
}
|
||||
|
||||
wrapper.setProperty(persistentProperty, val, false);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (T) entity;
|
||||
}
|
||||
}
|
||||
|
||||
private class ResourceSerializer extends StdSerializer<PersistentEntityResource<?>> {
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
|
||||
@@ -159,7 +159,7 @@ public abstract class AbstractWebIntegrationTests {
|
||||
String href = link.isTemplated() ? link.expand().getHref() : link.getHref();
|
||||
|
||||
MockHttpServletResponse response = mvc.perform(put(href).content(payload.toString()).contentType(mediaType)).//
|
||||
andExpect(status().isCreated()).//
|
||||
andExpect(status().is(both(greaterThanOrEqualTo(200)).and(lessThan(300)))).//
|
||||
andExpect(header().string("Location", is(notNullValue()))).//
|
||||
andReturn().getResponse();
|
||||
|
||||
@@ -259,6 +259,15 @@ public abstract class AbstractWebIntegrationTests {
|
||||
return (T) jsonPathResult;
|
||||
}
|
||||
|
||||
protected String assertJsonPathEquals(String path, MockHttpServletResponse response, String expected)
|
||||
throws Exception {
|
||||
|
||||
String jsonQueryResults = assertHasJsonPathValue(path, response);
|
||||
assertThat(jsonQueryResults, is(expected));
|
||||
|
||||
return jsonQueryResults;
|
||||
}
|
||||
|
||||
protected ResultMatcher hasLinkWithRel(final String rel) {
|
||||
|
||||
return new ResultMatcher() {
|
||||
|
||||
@@ -62,6 +62,8 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
@Autowired TestDataPopulator loader;
|
||||
@Autowired ResourceMappings mappings;
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#setUp()
|
||||
@@ -265,7 +267,7 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
|
||||
Link frodoSiblingLink = links.get(0);
|
||||
|
||||
putAndGet(frodoSiblingLink, toUriList(links.get(1), links.get(2), (links.get(3))), TEXT_URI_LIST);
|
||||
putAndGet(frodoSiblingLink, toUriList(links.get(1), links.get(2), links.get(3)), TEXT_URI_LIST);
|
||||
assertSiblingNames(frodoSiblingLink, "Bilbo", "Merry", "Pippin");
|
||||
|
||||
putAndGet(frodoSiblingLink, toUriList(links.get(3)), TEXT_URI_LIST);
|
||||
@@ -305,7 +307,6 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
public void propertiesCanHaveNulls() throws Exception {
|
||||
|
||||
Link peopleLink = discoverUnique("people");
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
Person frodo = new Person();
|
||||
frodo.setFirstName("Frodo");
|
||||
@@ -319,10 +320,33 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
assertNull(JsonPath.read(responseBody, "$.lastName"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-238
|
||||
*/
|
||||
@Test
|
||||
public void putShouldWorkDespiteExistingLinks() throws Exception {
|
||||
|
||||
Link peopleLink = discoverUnique("people");
|
||||
|
||||
Person frodo = new Person("Frodo", "Baggins");
|
||||
String frodoString = mapper.writeValueAsString(frodo);
|
||||
|
||||
MockHttpServletResponse createdPerson = postAndGet(peopleLink, frodoString, MediaType.APPLICATION_JSON);
|
||||
|
||||
Link frodoLink = assertHasLinkWithRel("self", createdPerson);
|
||||
assertJsonPathEquals("$.firstName", createdPerson, "Frodo");
|
||||
|
||||
String bilboWithFrodosLinks = createdPerson.getContentAsString().replace("Frodo", "Bilbo");
|
||||
|
||||
MockHttpServletResponse overwrittenResponse = putAndGet(frodoLink, bilboWithFrodosLinks, MediaType.APPLICATION_JSON);
|
||||
|
||||
assertHasLinkWithRel("self", overwrittenResponse);
|
||||
assertJsonPathEquals("$.firstName", overwrittenResponse, "Bilbo");
|
||||
}
|
||||
|
||||
private List<Link> preparePersonResources(Person primary, Person... persons) throws Exception {
|
||||
|
||||
Link peopleLink = discoverUnique("people");
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
MockHttpServletResponse primaryResponse = postAndGet(peopleLink, mapper.writeValueAsString(primary),
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.json;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
@@ -28,6 +43,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = RepositoryTestsConfig.class)
|
||||
@@ -57,6 +73,27 @@ public class PersistentEntitySerializationTests {
|
||||
assertThat(p.getSiblings(), is(Collections.EMPTY_LIST));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-238
|
||||
*/
|
||||
@Test
|
||||
public void deserializePersonWithLinks() throws IOException {
|
||||
|
||||
String bilbo = "{\n" + " \"_links\" : {\n" + " \"self\" : {\n"
|
||||
+ " \"href\" : \"http://localhost/people/4\"\n" + " },\n" + " \"siblings\" : {\n"
|
||||
+ " \"href\" : \"http://localhost/people/4/siblings\"\n" + " },\n" + " \"father\" : {\n"
|
||||
+ " \"href\" : \"http://localhost/people/4/father\"\n" + " }\n" + " },\n"
|
||||
+ " \"firstName\" : \"Bilbo\",\n" + " \"lastName\" : \"Baggins\",\n"
|
||||
+ " \"created\" : \"2014-01-31T21:07:45.574+0000\"\n" + "}\n";
|
||||
|
||||
Person p = mapper.readValue(bilbo, Person.class);
|
||||
assertThat(p.getFirstName(), equalTo("Bilbo"));
|
||||
assertThat(p.getLastName(), equalTo("Baggins"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-238
|
||||
*/
|
||||
@Test
|
||||
public void serializesPersonEntity() throws IOException, InterruptedException {
|
||||
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.json;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -20,11 +35,13 @@ import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Greg Trunquist
|
||||
*/
|
||||
@Configuration
|
||||
@Import({ JpaRepositoryConfig.class })
|
||||
@@ -87,6 +104,7 @@ public class RepositoryTestsConfig {
|
||||
mapper.registerModule(new Jackson2HalModule());
|
||||
mapper.registerModule(persistentEntityModule());
|
||||
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null));
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
{ "creator" : {
|
||||
"href" : "http://localhost:8080/persons/1"
|
||||
},
|
||||
"lineItems" : [
|
||||
{ "name" : "Java Chip" },
|
||||
{ "name" : "Chocolate Mocca " } ]
|
||||
{
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "http://localhost:8080/persons/1"
|
||||
}
|
||||
},
|
||||
"lineItems": [
|
||||
{
|
||||
"name": "Java Chip"
|
||||
},
|
||||
{
|
||||
"name": "Chocolate Mocca "
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user