FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.52k stars 1.38k forks source link

Combination of JsonMerge and JsonManagedReference fails #2055

Open miguelocarvajal opened 6 years ago

miguelocarvajal commented 6 years ago

I have an issue where if I use a List with both JsonMerge and JsonManagedReference annotations, the back reference is null.

I came up with the following test case:

Test class

package com.carvajalonline;

import java.util.List;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {
    public static void main(String[] args) {
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            Invoice invoice = objectMapper.readValue("{\"lines\": [{\"id\": 20, \"total\": \"$200.00\"}]}", Invoice.class);

            List<InvoiceLine> lines = invoice.getLines();
            for (InvoiceLine invoiceLine : lines) {
                System.out.println("line: " + invoiceLine.getId() + " - " + invoiceLine.getInvoice());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Invoice class

package com.carvajalonline;

import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonMerge;

public class Invoice {
    @JsonManagedReference("invoice")
    @JsonMerge
    private List<InvoiceLine> lines = new ArrayList<>();

    public Invoice() {
        lines.add(new InvoiceLine(10, "$100.00", this));
    }

    public List<InvoiceLine> getLines() {
        return lines;
    }

    public void setLines(List<InvoiceLine> lines) {
        this.lines = lines;
    }
}

InvoiceLine class

package com.carvajalonline;

import com.fasterxml.jackson.annotation.JsonBackReference;

public class InvoiceLine {
    private Integer id;
    private String total;
    @JsonBackReference("invoice")
    private Invoice invoice;

    public InvoiceLine() {

    }

    public InvoiceLine(Integer id, String total, Invoice invoice) {
        this.id = id;
        this.total = total;
        this.invoice = invoice;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTotal() {
        return total;
    }

    public void setTotal(String total) {
        this.total = total;
    }

    public Invoice getInvoice() {
        return invoice;
    }

    public void setInvoice(Invoice invoice) {
        this.invoice = invoice;
    }
}

I expect the output to be:

line: 10 - com.carvajalonline.Invoice@XXXX line: 20 - com.carvajalonline.Invoice@XXXX

However, when I introduce the JsonMerge annotation, the output I get is: line: 10 - com.carvajalonline.Invoice@XXXX line: 20 - null (SHOULD NOT BE GETTING NULL HERE)

If I remove the JsonMerge annotation, I get this output line: 20 - com.carvajalonline.Invoice@XXXX

Obviously when the JsonMerge annotation is removed, line 10 no longer appears because the array is being replaced, however the invoice is no longer null.

I am using version 2.9.5 to produce this bug. Any pointers as to how this can be handled?

Thanks!

miguelocarvajal commented 6 years ago

Dug through the code and it seems the problem is in MergingSettableBeanProperty.

Since it doesn't call the delegate, because it expects the null provider to do so from what I gather. If I add the annotation @JsonSetter(nulls = Nulls.SKIP) to the @JsonManagedReference above, I get the following error:

java.lang.IllegalStateException: Should never try to reset delegate
    at com.fasterxml.jackson.databind.deser.impl.ManagedReferenceProperty.withDelegate(ManagedReferenceProperty.java:43)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty$Delegating._with(SettableBeanProperty.java:663)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty$Delegating.withNullProvider(SettableBeanProperty.java:678)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty$Delegating.withNullProvider(SettableBeanProperty.java:678)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase._resolveMergeAndNullSettings(BeanDeserializerBase.java:919)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:516)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:477)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4190)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4009)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)

Just for testing I removed all of the if (value != null) checks in MergingSettableBeanProperty and removed the @JsonSetter(nulls = Nulls.SKIP) annotation and it works.

I am sure this is not the correct solution. Any thoughts?

dududen commented 4 months ago

in 2.14.2 version problem continues, any updates?

cowtowncoder commented 4 months ago

2.14.2 is an old version; should perhaps test with 2.17.1 (latest release).

But as a general rule: if issue has not updates, there is no update. This is likely the case here too. I am not aware of any back channels where updates would be made; fixes/changes should be reflected on issue.

One thing that would help here would be a proper test case (unit test) -- code sample uses print statements for manual verification; those would need to become assertions.

Beyond that, I am not sure combination of @JsonMerge and @JsonManagedReference can be made to work: it may be challenging to combine them.