Jackson을 사용하여 객체에 원시 JSON을 포함하려면 어떻게해야합니까?


102

객체가 Jackson을 사용하여 (비) 직렬화 될 때 Java 객체 내에 원시 JSON을 포함하려고합니다. 이 기능을 테스트하기 위해 다음 테스트를 작성했습니다.

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

@Test
public void test() throws JsonGenerationException, JsonMappingException, IOException {

    String foo = "one";
    String bar = "{\"A\":false}";

    Pojo pojo = new Pojo();
    pojo.foo = foo;
    pojo.bar = bar;

    String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}";

    ObjectMapper objectMapper = new ObjectMapper();
    String output = objectMapper.writeValueAsString(pojo);
    System.out.println(output);
    assertEquals(json, output);

    Pojo deserialized = objectMapper.readValue(output, Pojo.class);
    assertEquals(foo, deserialized.foo);
    assertEquals(bar, deserialized.bar);
}

코드는 다음 줄을 출력합니다.

{"foo":"one","bar":{"A":false}}

JSON은 내가 원하는 모습 그대로입니다. 불행히도 JSON을 객체로 다시 읽으려고 할 때 코드가 예외와 함께 실패합니다. 다음은 예외입니다.

org.codehaus.jackson.map.JsonMappingException : [출처 : java.io.StringReader@d70d7a; 줄 : 1, 열 : 13] (참조 체인을 통해 : com.tnal.prism.cobalt.gather.testing.Pojo [ "bar"])

Jackson이 한 방향으로 잘 작동하지만 다른 방향으로 갈 때 실패하는 이유는 무엇입니까? 다시 입력으로 자체 출력을 취할 수 있어야 할 것 같습니다. 내가하려는 것이 비 정통적이라는 것을 알고 있지만 (일반적인 조언은 bar라는 속성을 가진 내부 개체를 만드는 것입니다 A),이 JSON과 전혀 상호 작용하고 싶지 않습니다. 내 코드는이 코드에 대한 패스 스루 역할을합니다. JSON이 변경 될 때 코드를 수정할 필요가 없기 때문에이 JSON을 가져 와서 아무것도 건드리지 않고 다시 보내고 싶습니다.

충고 감사합니다.

편집 : Pojo를 정적 클래스로 만들어 다른 오류를 발생 시켰습니다.

답변:


64

@JsonRawValue는 역방향이 처리하기가 약간 까다롭기 때문에 직렬화 측 전용입니다. 실제로 미리 인코딩 된 콘텐츠를 삽입 할 수 있도록 추가되었습니다.

역방향에 대한 지원을 추가하는 것이 가능할 것이라고 생각합니다. 매우 어색 할 것입니다. 내용을 구문 분석 한 다음 "원시"형식으로 다시 작성해야합니다.이 형식은 동일하거나 같지 않을 수 있습니다 (문자 인용 이후 다를 수 있음). 이것은 일반적인 경우입니다. 그러나 아마도 문제의 일부 하위 집합에 대해 의미가있을 것입니다.

그러나 특정 경우에 대한 해결 방법은 유형을 'java.lang.Object'로 지정하는 것입니다. 이는 정상적으로 작동해야하므로 직렬화의 경우 String이 그대로 출력되고 deserialization의 경우 다음과 같이 deserialize됩니다. 지도. 실제로 만약 그렇다면 별도의 getter / setter를 원할 수도 있습니다. getter는 직렬화를 위해 문자열을 반환합니다 (@JsonRawValue 필요). setter는 Map 또는 Object를 사용합니다. 의미가 있다면 문자열로 다시 인코딩 할 수 있습니다.


1
이것은 매력처럼 작동합니다. 코드에 대한 내 응답을 참조하십시오 ( 주석의 형식은 awgul입니다 ).
yves amsellem

나는 이것에 대해 다른 사용 사례를 가졌습니다. deser / ser에서 많은 문자열 쓰레기를 생성하고 싶지 않다면 문자열을 그냥 통과시킬 수 있어야합니다. 이것을 추적하는 스레드를 보았지만 가능한 기본 지원이없는 것 같습니다. markmail.org/message/…를
Sid

@Sid와 토큰 화를 효율적으로 수행 할 수있는 방법이 없습니다. 처리되지 않은 토큰의 통과를 지원하려면 추가 상태 유지가 필요하므로 "일반"구문 분석의 효율성이 다소 떨어집니다. 정규 코드와 예외 발생 사이의 최적화와 유사합니다. 후자를 지원하면 전자에 오버 헤드가 추가됩니다. Jackson은 처리되지 않은 입력을 계속 사용할 수 있도록 설계되지 않았습니다. (오류 메시지에도) 가지고 있으면 좋지만 다른 접근 방식이 필요합니다.
StaxMan

55

다음 @StaxMan 대답, 나는 마치 마법처럼 다음과 같은 일을했습니다 :

public class Pojo {
  Object json;

  @JsonRawValue
  public String getJson() {
    // default raw value: null or "[]"
    return json == null ? null : json.toString();
  }

  public void setJson(JsonNode node) {
    this.json = node;
  }
}

그리고 초기 질문에 충실하기 위해 다음은 작업 테스트입니다.

public class PojoTest {
  ObjectMapper mapper = new ObjectMapper();

  @Test
  public void test() throws IOException {
    Pojo pojo = new Pojo("{\"foo\":18}");

    String output = mapper.writeValueAsString(pojo);
    assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}");

    Pojo deserialized = mapper.readValue(output, Pojo.class);
    assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}");
    // deserialized.json == {"foo":18}
  }
}

1
시도하지 않았지만 작동해야합니다. 1) Object json 대신 JsonNode 노드 만들기 2) toString () 대신 node.asText () 사용. 그래도 두 번째에 대해서는 잘 모르겠습니다.
Vadim Kirilchuk 2015-08-28

결국 A를 getJson()반환하는 이유 가 궁금 String합니다. JsonNodesetter를 통해 설정된을 방금 반환했다면 원하는대로 직렬화됩니다.
sorrymissjackson

@VadimKirilchuk node.asText()은 반대쪽에 빈 값을 반환합니다 toString().
v.ladynev

36

나는 사용자 정의 디시리얼라이저이 작업을 수행 할 수 있었다 (잘라 내기 및 붙여 넣을 여기 )

package etc;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

/**
 * Keeps json value as json, does not try to deserialize it
 * @author roytruelove
 *
 */
public class KeepAsJsonDeserialzier extends JsonDeserializer<String> {

    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        TreeNode tree = jp.getCodec().readTree(jp);
        return tree.toString();
    }
}

6
얼마나 간단한 지 놀랍습니다. IMO 이것은 공식적인 대답이어야합니다. 배열, 하위 객체 등을 포함하는 매우 복잡한 구조로 시도했습니다. 대답을 편집하고 역 직렬화 할 String 멤버에 @JsonDeserialize (using = KeepAsJsonDeserialzier.class) 주석을 추가해야합니다. (그리고 클래스 이름의 오타 수정 ;-)
Heri

이것은 Deserializion에서 작동합니다. 원시 json을 pojo로 직렬화하는 것은 어떻습니까? 그것이 어떻게 성취 될
것인가

4
직렬화, 사용 @xtrakBandit@JsonRawValue
smartwjw

이것은 매력처럼 작동합니다. 감사합니다. Roy와 @Heri ..이 게시물의 조합과 Heri의 의견은 imho가 최고의 답변입니다.
Michal

간단하고 깔끔한 솔루션. 나는 @Heri에 동의
마헤 nanayakkara

18

@JsonSetter가 도움이 될 수 있습니다. 내 샘플을 참조하십시오 ( '데이터'는 구문 분석되지 않은 JSON을 포함해야 함).

class Purchase
{
    String data;

    @JsonProperty("signature")
    String signature;

    @JsonSetter("data")
    void setData(JsonNode data)
    {
        this.data = data.toString();
    }
}

3
JsonNode.toString () 문서에 따르면 개발자가 읽을 수있는 노드 표현을 생성합니다. 유효한 JSON이 <b> 또는 아닐 수도 있습니다 </ b>. 그래서 이것은 실제로 매우 위험한 구현입니다.
Piotr

@Piotr the javadoc은 "(Jackson 2.10 기준) 유효한 JSON을 문자열로 databind의 기본 설정을 사용하여 생성하는 방법"이라고 말합니다.
bernie

4

Roy Truelove 의 훌륭한 답변에 추가하여 다음 과 같은 모양에 대한 응답으로 맞춤형 디시리얼라이저를 주입하는 방법입니다 @JsonRawValue.

import com.fasterxml.jackson.databind.Module;

@Component
public class ModuleImpl extends Module {

    @Override
    public void setupModule(SetupContext context) {
        context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl());
    }
}

import java.util.Iterator;

import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;

public class BeanDeserializerModifierImpl extends BeanDeserializerModifier {
    @Override
    public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
        Iterator<SettableBeanProperty> it = builder.getProperties();
        while (it.hasNext()) {
            SettableBeanProperty p = it.next();
            if (p.getAnnotation(JsonRawValue.class) != null) {
                builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true);
            }
        }
        return builder;
    }
}

이것은 Jackson 2.9에서 작동하지 않습니다. 현재 PropertyBasedCreator.construct에서 하나를 대체하는 대신 이전 속성을 사용하기 때문에 깨진 것처럼 보입니다
dant3

3

이것은 내부 클래스의 문제입니다. Pojo클래스는이다 비 정적 내부 클래스 테스트 클래스, 그리고 잭슨은하지 인스턴스화 클래스 수 있습니다. 따라서 직렬화 할 수 있지만 역 직렬화 할 수는 없습니다.

다음과 같이 클래스를 재정의하십시오.

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

추가 참고 static


감사. 한 걸음 더 나아 갔지만 이제는 다른 오류가 발생합니다. 새로운 오류로 원본 게시물을 업데이트하겠습니다.
bhilstrom 2011 년

3

이 쉬운 솔루션이 저에게 효과적이었습니다.

public class MyObject {
    private Object rawJsonValue;

    public Object getRawJsonValue() {
        return rawJsonValue;
    }

    public void setRawJsonValue(Object rawJsonValue) {
        this.rawJsonValue = rawJsonValue;
    }
}

그래서 JSON의 원시 값을 rawJsonValue 변수에 저장할 수 있었고 다른 필드와 함께 (객체로) 역 직렬화하여 JSON으로 다시 보내고 REST를 통해 보내는 데 문제가 없었습니다. @JsonRawValue를 사용하면 저장된 JSON이 객체가 아닌 문자열로 역 직렬화되어 내가 원하는 것이 아니기 때문에 도움이되지 않았습니다.


3

이것은 JPA 엔티티에서도 작동합니다.

private String json;

@JsonRawValue
public String getJson() {
    return json;
}

public void setJson(final String json) {
    this.json = json;
}

@JsonProperty(value = "json")
public void setJsonRaw(JsonNode jsonNode) {
    // this leads to non-standard json, see discussion: 
    // setJson(jsonNode.toString());

    StringWriter stringWriter = new StringWriter();
    ObjectMapper objectMapper = new ObjectMapper();
    JsonGenerator generator = 
      new JsonFactory(objectMapper).createGenerator(stringWriter);
    generator.writeTree(n);
    setJson(stringWriter.toString());
}

이상적으로는 ObjectMapper 및 JsonFactory도 컨텍스트에서 가져오고 JSON을 올바르게 처리하도록 구성됩니다 (예 : 'Infinity'float와 같은 비표준 값 또는 표준).


1
JsonNode.toString()문서 에 따르면 Method that will produce developer-readable representation of the node; which may <b>or may not</b> be as valid JSON.이것은 실제로 매우 위험한 구현입니다.
Piotr

안녕하세요 @Piotr, 힌트 주셔서 감사합니다. 물론 이것은 JsonNode.asText()내부적으로 사용되며 Infinity 및 기타 비표준 JSON 값을 출력합니다.
Georg

@Piotr the javadoc은 "(Jackson 2.10 기준) 유효한 JSON을 문자열로 databind의 기본 설정을 사용하여 생성하는 방법"이라고 말합니다.
bernie

2

다음은 Jackson 모듈을 사용하여 @JsonRawValue양방향 (직렬화 및 역 직렬화) 작업을 수행하는 방법에 대한 전체 작업 예제입니다 .

public class JsonRawValueDeserializerModule extends SimpleModule {

    public JsonRawValueDeserializerModule() {
        setDeserializerModifier(new JsonRawValueDeserializerModifier());
    }

    private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier {
        @Override
        public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
            builder.getProperties().forEachRemaining(property -> {
                if (property.getAnnotation(JsonRawValue.class) != null) {
                    builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true);
                }
            });
            return builder;
        }
    }

    private static class JsonRawValueDeserializer extends JsonDeserializer<String> {
        private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            return p.readValueAsTree().toString();
        }
    }
}

그런 다음 다음을 생성 한 후 모듈을 등록 할 수 있습니다 ObjectMapper.

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());

String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);

위의 것 외에해야 할 일이 있습니까? 나는 JsonRawValueDeserializer의 deserialize 메소드가 ObjectMapper에 의해 절대 호출되지 않는다는 것을 발견했습니다
Michael Coxon

@MichaelCoxon 작동하게 만들었습니까? 과거에 나에게 문제를 일으킨 한 가지는 org.codehaus.jackson깨닫지 못한 채 패키지의 주석을 사용하는 것이 었 습니다. 모든 수입품이 com.fasterxml.jackson.
Helder Pereira


1

객체를 사용하면 두 가지 방식으로 모두 잘 작동합니다.이 방법은 원시 값을 두 번에 역 직렬화하는 데 약간의 오버 헤드가 있습니다.

ObjectMapper mapper = new ObjectMapper();
RawJsonValue value = new RawJsonValue();
value.setRawValue(new RawHello(){{this.data = "universe...";}});
String json = mapper.writeValueAsString(value);
System.out.println(json);
RawJsonValue result = mapper.readValue(json, RawJsonValue.class);
json = mapper.writeValueAsString(result.getRawValue());
System.out.println(json);
RawHello hello = mapper.readValue(json, RawHello.class);
System.out.println(hello.data);

RawHello.java

public class RawHello {

    public String data;
}

RawJsonValue.java

public class RawJsonValue {

    private Object rawValue;

    public Object getRawValue() {
        return rawValue;
    }

    public void setRawValue(Object value) {
        this.rawValue = value;
    }
}

1

비슷한 문제가 있었지만 JSON itens ( List<String>) 가 많은 목록을 사용했습니다 .

public class Errors {
    private Integer status;
    private List<String> jsons;
}

@JsonRawValue주석을 사용하여 직렬화를 관리했습니다 . 그러나 deserialization을 위해 Roy의 제안에 따라 사용자 지정 deserializer 를 만들어야했습니다 .

public class Errors {

    private Integer status;

    @JsonRawValue
    @JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
    private List<String> jsons;

}

아래에서 내 "목록"디시리얼라이저를 볼 수 있습니다.

public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {

    @Override
    public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
        if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
            final List<String> list = new ArrayList<>();
            while (jp.nextToken() != JsonToken.END_ARRAY) {
                list.add(jp.getCodec().readTree(jp).toString());
            }
            return list;
        }
        throw cxt.instantiationException(List.class, "Expected Json list");
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.