Jackson kullanarak bir nesneye ham JSON'u nasıl dahil edebilirim?


103

Nesne Jackson kullanılarak (de) serileştirildiğinde bir Java nesnesinin içine ham JSON eklemeye çalışıyorum. Bu işlevi test etmek için aşağıdaki testi yazdım:

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

Kod, aşağıdaki satırı verir:

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

JSON, işlerin tam olarak nasıl görünmesini istediğim. Ne yazık ki kod, JSON'u nesneye geri okumaya çalışırken bir istisna ile başarısız oluyor. İşte istisna:

org.codehaus.jackson.map.JsonMappingException: [Kaynak: java.io.StringReader@d70d7a; satır: 1, sütun: 13] (referans zinciri aracılığıyla: com.tnal.prism.cobalt.gather.testing.Pojo ["bar"])

Jackson neden bir yönde gayet iyi işliyor ama diğer yöne giderken başarısız oluyor? Görünüşe göre kendi çıktısını tekrar girdi olarak alabilmelidir. Yapmaya çalıştığım şeyin alışılmışın dışında olduğunu biliyorum (genel tavsiye, baradlandırılmış bir özelliğe sahip bunun için bir iç nesne yaratmaktır A), ancak bu JSON ile hiç etkileşimde bulunmak istemiyorum. Kodum bu kod için bir geçiş işlevi görüyor - Bu JSON'u alıp hiçbir şeye dokunmadan tekrar göndermek istiyorum, çünkü JSON değiştiğinde kodumun değişikliklere ihtiyaç duymasını istemiyorum.

Tavsiye için teşekkürler.

DÜZENLEME: Pojo'yu statik bir sınıf yaptı, bu da farklı bir hataya neden oluyordu.

Yanıtlar:


65

@JsonRawValue yalnızca serileştirme tarafına yöneliktir, çünkü ters yönün işlenmesi biraz daha zordur. Gerçekte, önceden kodlanmış içeriğin enjekte edilmesine izin vermek için eklenmiştir.

Sanırım tersine destek eklemek mümkün olabilirdi, ancak bu oldukça garip olurdu: içeriğin ayrıştırılması ve ardından aynı olabilecek veya olmayabilecek "ham" biçime yeniden yazılması gerekecek (karakter alıntılarından beri farklı olabilir). Bu genel durum için. Ama belki bazı alt problemler için mantıklı olacaktır.

Ama bence sizin özel durumunuz için bir çözüm, türü 'java.lang.Object' olarak belirtmek olacaktır, çünkü bu iyi çalışmalıdır: serileştirme için, String olduğu gibi çıktısı alınacak ve serileştirme için, şu şekilde seri kaldırılacaktır. bir harita. Aslında, eğer öyleyse ayrı bir alıcı / ayarlayıcıya sahip olmak isteyebilirsiniz; getter serileştirme için String'i döndürür (ve @JsonRawValue'ye ihtiyaç duyar); ve ayarlayıcı Harita veya Nesne alır. Bu mantıklıysa, onu bir String olarak yeniden kodlayabilirsiniz.


1
Bu bir cazibe gibi çalışıyor; kod için yanıtıma bakın ( yorumlarda biçimlendirme awgul ).
yves amsellem

Bunun için farklı bir kullanım durumum vardı. Görünüşe göre, çölde çok fazla dizi çöpü üretmek istemiyorsak, bir dizeyi bu şekilde geçirebilmeliyiz. Bunu izleyen bir ileti dizisi gördüm, ancak yerel destek mümkün değil gibi görünüyor. Markmail.org/message/… '
Sid

@Sid, bunu VE tokenleştirmeyi hem verimli bir şekilde yapmanın bir yolu yok. İşlenmemiş simgelerin geçişini desteklemek için, "düzenli" ayrıştırmayı bir şekilde daha az verimli kılan ek durum koruma gerektirecektir. Bu, normal kod ile istisna atma arasındaki bir tür optimizasyon gibidir: ikincisini desteklemek, eski için ek yük getirir. Jackson, işlenmemiş girdileri kullanılabilir tutmaya çalışmak üzere tasarlanmamıştır; etrafta olması güzel olurdu (hata mesajları için de), ancak farklı bir yaklaşım gerektirir.
StaxMan

55

@StaxMan cevabını takiben , aşağıdaki işleri bir cazibe gibi yaptım:

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

Ve ilk soruya sadık kalmak için, işte çalışma testi:

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
Denemedim ama işe yaramalı: 1) Object json yerine bir JsonNode düğümü yapın 2) toString () yerine node.asText () kullanın. Yine de 2. olandan emin değilim.
Vadim Kirilchuk

Acaba neden sonuçta getJson()a dönüyor String? JsonNodeAyarlayıcı aracılığıyla ayarlanmış olanı yeni döndürdüyse, istenildiği gibi serileştirilir, değil mi?
sorrymissjackson

@VadimKirilchuk node.asText(), tersi boş değer döndürür toString().
v.ladynev

37

Bunu özel bir seri çözücü ile yapabildim ( buradan kesip yapıştırarak )

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
Ne kadar basit. IMO bu resmi cevap olmalıdır. Diziler, alt nesneler, vb. İçeren çok karmaşık bir yapı ile denedim. Belki cevabınızı düzenlersiniz ve serileştirilmeyecek String üyesine @JsonDeserialize (= KeepAsJsonDeserialzier.class kullanarak) tarafından açıklama eklenmesi gerektiğini eklersiniz. (ve sınıf adınızdaki yazım hatasını düzeltin ;-)
Heri

bu Deserializion için çalışıyor. Ham json'un bir pojo'ya serileştirilmesine ne dersiniz? Bu nasıl başarılırdı
xtrakBandit

4
@xtrakBandit for Serialization, use@JsonRawValue
smartwjw

Bu bir cazibe gibi çalışıyor. Teşekkürler Roy ve @Heri .. Heri'nin yorumuyla birlikte bu yazının kombinasyonu en iyi cevap imho.
Michal

Basit ve temiz bir çözüm. @Heri
mahesh nanayakkara

18

@JsonSetter yardımcı olabilir. Örneğime bakın ('veriler' ayrıştırılmamış JSON içermelidir):

class Purchase
{
    String data;

    @JsonProperty("signature")
    String signature;

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

3
JsonNode.toString () dokümantasyon yöntemine göre, düğümün geliştirici tarafından okunabilir gösterimini üretecek yöntem; geçerli JSON olarak <b> olabilir veya olmayabilir </b>. Yani bu aslında çok riskli bir uygulama.
Piotr

@Piotr the javadoc artık "databind'in varsayılan ayarlarını kullanarak (Dize olarak) geçerli JSON üretecek yöntem (Jackson 2.10 itibarıyla)" diyor
bernie

4

Ekleme Roy Truelove bireyin büyük bir cevap , bu görünüm cevaben özel deserialiser enjekte nasıl @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;
    }
}

bu Jackson 2.9'da çalışmaz. Görünüşe göre kırılmış gibi görünüyor çünkü artık birini değiştirmek yerine PropertyBasedCreator.construct'ta eski özelliği kullanıyor
dant3

3

Bu, iç sınıflarınızla ilgili bir sorundur. PojoSınıf olan statik olmayan iç sınıf test sınıfının ve Jackson değil örneğini o sınıf can. Böylece serileştirebilir, ancak seriyi kaldıramaz.

Sınıfınızı şu şekilde yeniden tanımlayın:

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

Eklendiğine dikkat edin static


Teşekkürler. Bu beni bir adım daha ileri götürdü ama şimdi farklı bir hata alıyorum. Orijinal gönderiyi yeni hatayla güncelleyeceğim.
bhilstrom

3

Bu kolay çözüm benim için çalıştı:

public class MyObject {
    private Object rawJsonValue;

    public Object getRawJsonValue() {
        return rawJsonValue;
    }

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

Böylece, ham JSON değerini rawJsonValue değişkeninde depolayabildim ve ardından onu (nesne olarak) diğer alanlarla birlikte JSON'a geri döndürmek ve REST'im aracılığıyla göndermek sorun olmadı. @JsonRawValue kullanmak bana yardımcı olmadı çünkü depolanan JSON, nesne olarak değil, String olarak serileştirildi ve istediğim şey bu değildi.


3

Bu, bir JPA varlığında bile çalışır:

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

İdeal olarak, ObjectMapper ve hatta JsonFactory bağlamdan gelir ve JSON'unuzu doğru şekilde işleyecek şekilde yapılandırılır (örneğin, standart veya 'Infinity' kayan değerler gibi standart dışı değerlerle).


1
JsonNode.toString()Belgelere göre Method that will produce developer-readable representation of the node; which may <b>or may not</b> be as valid JSON.Yani bu aslında çok riskli bir uygulama.
Piotr

Merhaba @Piotr, ipucu için teşekkürler. Elbette haklısınız, bu JsonNode.asText()dahili olarak kullanır ve Infinity ve diğer standart olmayan JSON değerlerini verir.
Georg

@Piotr the javadoc artık "databind'in varsayılan ayarlarını kullanarak (Dize olarak) geçerli JSON üretecek yöntem (Jackson 2.10 itibarıyla)" diyor
bernie

2

Jackson modüllerinin @JsonRawValueher iki şekilde de (serileştirme ve seriyi kaldırma) nasıl kullanılacağına dair eksiksiz bir çalışma örneği :

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

Daha sonra modülü oluşturduktan sonra kaydedebilirsiniz ObjectMapper:

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

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

Yukarıdakilere ek olarak yapmanız gereken başka bir şey var mı? JsonRawValueDeserializer'ın seriyi kaldırma yönteminin ObjectMapper tarafından asla çağrılmadığını buldum
Michael Coxon

@MichaelCoxon Çalıştırmayı başardınız mı? Geçmişte sorunlara neden olan bir şey, org.codehaus.jacksonpaketin açıklamalarını fark etmeden kullanmaktı. Tüm ithalatlarınızın nereden geldiğinden emin olun com.fasterxml.jackson.
Helder Pereira


1

Bir nesnenin kullanılması her iki şekilde de iyi çalışır ... Bu yöntemin, ham değeri iki kez serileştirmeden çıkarılması için biraz ek yük vardır.

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

Benzer bir sorun yaşadım, ancak çok sayıda JSON itens ( List<String>) içeren bir liste kullanıyorum .

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

@JsonRawValueEk açıklamayı kullanarak serileştirmeyi yönettim. Ancak seriyi kaldırma için Roy'un önerisine göre özel bir seri çözümleyici oluşturmam gerekiyordu .

public class Errors {

    private Integer status;

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

}

Aşağıda "Liste" serisini kaldırıcımı görebilirsiniz.

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");
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.