fastjson set方法的属性依赖,转对象的部分属性为空

本文分析了FastJSON在反序列化过程中出现的部分属性丢失问题,对比了Gson和Jackson的表现,深入探讨了FastJSON源码,揭示了其反序列化顺序的影响,并提出了解决方案。

前言

       最近做项目,发现fastjson在parseObject的过程中,出现部分属性丢失,然而json字符串确是存在属性值的,然后试了gson,发现正常,但是jackson某些情况下也存在问题。下面分析

1. demo

<dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.10.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.10.0</version>
        </dependency>



    </dependencies>

bean

package com.feng.fastjson.bean;

import lombok.ToString;

@ToString
public class Person {

    private String sex;

    private String zoo;

    private String beard;

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getBeard() {
        return beard;
    }

    public void setBeard(String beard) {
        if ("Man".equals(sex)) {
            if (this.beard == null) {
                this.beard = "man has";
            } else {
                this.beard = beard;
            }
        }
    }

    public String getZoo() {
        return zoo;
    }

    public void setZoo(String zoo) {
        if ("Man".equals(sex)) {
            if (this.zoo == null) {
                this.zoo = "zoo";
            } else {
                this.zoo = zoo;
            }
        }
    }
}

bean是特殊加工的,不是原生set方法,是根据业务需求改造后,属性之间有依赖和相关性。

main方法

package com.feng.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.feng.fastjson.bean.Person;
import com.google.gson.Gson;

import java.io.IOException;

public class Main {

    public static void main(String[] args) throws IOException {
        Person person = new Person();
        person.setSex("Man");
        person.setBeard("has");
        person.setZoo("zoo");

        String jsonStr = JSONObject.toJSONString(person);
        System.out.println("json\t"+jsonStr);
        System.out.println();

        String str = "{\"sex\":\"Man\",\"beard\":\"man has\",\"zoo\":\"zoo\"}";
        Person person1 = JSON.parseObject(jsonStr, Person.class);
        System.out.println("fastjson:\t"+person1);
        Person person11 = JSON.parseObject(str, Person.class);
        System.out.println("fastjson 11:\t"+person11);
        System.out.println();

        Person person2 = new Gson().fromJson(jsonStr, Person.class);
        System.out.println("gson:\t"+person2);
        System.out.println();

        ObjectMapper mapper = new ObjectMapper();
        Person person3 = mapper.readValue(jsonStr.getBytes(), Person.class);
        System.out.println("jackson:\t"+person3);
        Person person33 = mapper.readValue(str.getBytes(), Person.class);
        System.out.println("jackson 33:\t"+person33);
    }
}

2. 结果

运行上面的main

json	{"beard":"man has","sex":"Man","zoo":"zoo"}

fastjson:	Person(sex=Man, zoo=zoo, beard=null)
fastjson 11:	Person(sex=Man, zoo=zoo, beard=man has)

gson:	Person(sex=Man, zoo=zoo, beard=man has)

jackson:	Person(sex=Man, zoo=zoo, beard=null)
jackson 33:	Person(sex=Man, zoo=zoo, beard=man has)

可以看出,除了gson外,fastjson与jackson在反序列化的过程中,某些情况丢失了属性值

根据上面的测试,可以初步推断,set方法在fastjson与jackson中是按照json字符串的顺序转化生成bean的。在set时,由于json字符串的属性的字段先后顺序,造成部分属性设置依赖的其他属性未设置,从而丢失属性字段。

3. fastjson源码分析

直接跟踪源码,发现

继续,创建反序列化器

 然后反序列化json字符串,使用ASM字节码生成技术,动态生产反序列化器对象

核心关注反序列化方法,将code生成class文件,反编译

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.alibaba.fastjson.parser.deserializer;

import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONLexerBase;
import com.alibaba.fastjson.parser.ParseContext;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.JavaBeanInfo;
import com.feng.fastjson.bean.Person;
import java.lang.reflect.Type;

public class FastjsonASMDeserializer_1_Person extends JavaBeanDeserializer {
    public char[] zoo_asm_prefix__ = "\"zoo\":".toCharArray();
    public char[] sex_asm_prefix__ = "\"sex\":".toCharArray();
    public char[] beard_asm_prefix__ = "\"beard\":".toCharArray();
    public ObjectDeserializer zoo_asm_deser__;
    public ObjectDeserializer sex_asm_deser__;
    public ObjectDeserializer beard_asm_deser__;

    public FastjsonASMDeserializer_1_Person(ParserConfig var1, JavaBeanInfo var2) {
        super(var1, var2);
    }

    public Object createInstance(DefaultJSONParser var1, Type var2) {
        return new Person();
    }

    public Object deserialze(DefaultJSONParser var1, Type var2, Object var3, int var4) {
        JSONLexerBase var5 = (JSONLexerBase)var1.lexer;
        if (var5.token() == 14 && var5.isEnabled(var4, 8192)) {
            return this.deserialzeArrayMapping(var1, var2, var3, (Object)null);
        } else if (var5.isEnabled(512) && var5.scanType("com.feng.fastjson.bean.Person") != -1) {
            Person var8;
            int var12;
            String var14;
            String var15;
            String var16;
            label108: {
                ParseContext var6 = var1.getContext();
                int var7 = 0;
                var8 = new Person();
                ParseContext var9 = var1.getContext();
                ParseContext var10 = var1.setContext(var9, var8, var3);
                if (var5.matchStat != 4) {
                    boolean var11 = false;
                    var12 = 0;
                    boolean var13 = var5.isEnabled(4096);
                    String var10000;
                    if (var13) {
                        var12 |= 1;
                        var10000 = var5.stringDefaultValue();
                    } else {
                        var10000 = null;
                    }

                    var14 = (String)var10000;
                    if (var13) {
                        var12 |= 2;
                        var10000 = var5.stringDefaultValue();
                    } else {
                        var10000 = null;
                    }

                    var15 = (String)var10000;
                    if (var13) {
                        var12 |= 4;
                        var10000 = var5.stringDefaultValue();
                    } else {
                        var10000 = null;
                    }

                    var16 = (String)var10000;
                    var14 = var5.scanFieldString(this.beard_asm_prefix__);
                    if (var5.matchStat > 0) {
                        var12 |= 1;
                    }

                    if (var5.matchStat == -1) {
                        break label108;
                    }

                    label110: {
                        if (var5.matchStat > 0) {
                            ++var7;
                            if (var5.matchStat == 4) {
                                break label110;
                            }
                        }

                        var15 = var5.scanFieldString(this.sex_asm_prefix__);
                        if (var5.matchStat > 0) {
                            var12 |= 2;
                        }

                        if (var5.matchStat == -1) {
                            break label108;
                        }

                        if (var5.matchStat > 0) {
                            ++var7;
                            if (var5.matchStat == 4) {
                                break label110;
                            }
                        }

                        var16 = var5.scanFieldString(this.zoo_asm_prefix__);
                        if (var5.matchStat > 0) {
                            var12 |= 4;
                        }

                        if (var5.matchStat == -1) {
                            break label108;
                        }

                        if (var5.matchStat > 0) {
                            ++var7;
                            if (var5.matchStat == 4) {
                                break label110;
                            }
                        }

                        if (var5.matchStat != 4) {
                            break label108;
                        }
                    }

                    if ((var12 & 1) != 0) {
                        var8.setBeard(var14);
                    }

                    if ((var12 & 2) != 0) {
                        var8.setSex(var15);
                    }

                    if ((var12 & 4) != 0) {
                        var8.setZoo(var16);
                    }
                }

                var1.setContext(var9);
                if (var10 != null) {
                    var10.object = var8;
                }

                return var8;
            }

            if ((var12 & 1) != 0) {
                var8.setBeard(var14);
            }

            if ((var12 & 2) != 0) {
                var8.setSex(var15);
            }

            if ((var12 & 4) != 0) {
                var8.setZoo(var16);
            }

            return (Person)this.parseRest(var1, var2, var3, var8, var4, new int[]{var12});
        } else {
            return super.deserialze(var1, var2, var3, var4);
        }
    }

    public Object deserialzeArrayMapping(DefaultJSONParser var1, Type var2, Object var3, Object var4) {
        JSONLexerBase var12 = (JSONLexerBase)var1.lexer;
        String var5 = var12.scanTypeName(var1.getSymbolTable());
        if (var5 != null) {
            JavaBeanDeserializer var6 = JavaBeanDeserializer.getSeeAlso(var1.getConfig(), super.beanInfo, var5);
            if (var6 instanceof JavaBeanDeserializer) {
                return var6.deserialzeArrayMapping(var1, var2, var3, var12);
            }
        }

        Person var7 = new Person();
        String var8 = var12.scanString(',');
        String var9 = var12.scanString(',');
        String var10 = var12.scanString(']');
        var7.setZoo(var10);
        var7.setSex(var9);
        var7.setBeard(var8);
        char var11;
        if ((var11 = var12.getCurrent()) == ',') {
            var12.next();
            var12.setToken(16);
        } else if (var11 == ']') {
            var12.next();
            var12.setToken(15);
        } else if (var11 == 26) {
            var12.next();
            var12.setToken(20);
        } else {
            var12.nextToken(16);
        }

        return var7;
    }
}

可以看到label108起着顺序的作用,默认时通过json字符串的顺序反序列化的。顺序是可以控制的,即可以对属性field先排序再反序列化。

总结

       在使用fastjson进行序列化与反序列化的过程中尽量使用JDK默认的set get方法,如果自定义get与set方法,建议控制顺序,避免序列化与反序列化过程中属性的丢失。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值