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

객체가 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를 정적 클래스로 만들어 다른 오류를 발생 시켰습니다.



답변

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

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

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


답변

다음 @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}
  }
}


답변

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

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();
    }
}


답변

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

class Purchase
{
    String data;

    @JsonProperty("signature")
    String signature;

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


답변

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;
    }
}


답변

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

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

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

추가 참고 static


답변

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

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이 객체가 아닌 문자열로 역 직렬화되어 내가 원하는 것이 아니기 때문에 도움이되지 않았습니다.