核心技术-1、自定义JSON解析

原文地址:https://www.cnblogs.com/codelove/p/5236488.html

一、返回数据结构分析

1、数据示例:

{"menu":
    {"button":[
        {"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]},
        {"type":"click","name":"歌手简介","key":"V1001_TODAY_SINGER","sub_button":[]},
        {"name":"菜单","sub_button":[
            {"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]},
            {"type":"view","name":"视频","url":"http://v.qq.com/","sub_button":[]},
            {"type":"click","name":"赞一下我们","key":"V1001_GOOD","sub_button":[]}
        ]}
    ]}
}

2、数据分析:

图1-1微信自定义菜单数据分析

3、解决方案:

    public class MenuFull_RootButton
    {
        public string type { get; set; }
        public string key { get; set; }
        public string name { get; set; }
        public string url { get; set; }
        public string media_id { get; set; }
        public List<MenuFull_RootButton> sub_button { get; set; }
    }

二、序列化解决方案

1、定义接口方法:

    /// <summary>
    /// 自定义菜单接口
    /// http://mp.weixin.qq.com/wiki/10/0234e39a2025342c17a7d23595c6b40a.html
    /// </summary>
    public class MenuApi : ApiBase
    {
        const string APIName = "menu";
        /// <summary>
        /// 自定义菜单查询接口
        /// https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
        /// </summary>
        /// <returns>菜单返回结果</returns>
        public MenuGetApiResultModel Get()
        {
            //获取api请求url
            var url = GetAccessApiUrl("get", APIName);
            return Get<MenuGetApiResultModel>(url, new MenuButtonsCustomConverter());
        }
    }

重点是:MenuGetApiResultModelMenuButtonsCustomConverter

注意:ApiBase和Get的封装请暂时忽略。Get在这里只是用于发起Get请求并且序列化JSON而已,其定义如下:

/// <summary>
/// GET提交请求,返回ApiResult对象
/// </summary>
/// <typeparam name="T">ApiResult对象</typeparam>
/// <param name="url">请求地址</param>
/// <param name="jsonConverts">Json转换器</param>
/// <returns>ApiResult对象</returns>
protected T Get<T>(string url, params JsonConverter[] jsonConverts) where T : ApiResult

2、定义JSON模型:

1)定义根
    /// <summary>
    /// 菜单返回结果
    /// </summary>
    public class MenuGetApiResultModel : ApiResult
    {
        [JsonProperty("menu")]
        public MenuInfo Menu { get; set; }
    }
    /// <summary>
    /// 菜单信息
    /// </summary>
    public class MenuInfo
    {
        [JsonProperty("button")]
        public List<MenuButtonBase> Button { get; set; }
    }
2)定义菜单类型:
    /// <summary>
    /// 菜单类型
    /// </summary>
    public enum MenuButtonTypes
    {
        /// <summary>
        /// 点击推事件
        /// </summary>
        click = 1,
        /// <summary>
        /// 跳转URL
        /// </summary>
        view = 2,
        /// <summary>
        /// 扫码推事件
        /// </summary>
        scancode_push = 3,
        /// <summary>
        /// 扫码推事件且弹出“消息接收中”提示框
        /// </summary>
        scancode_waitmsg = 4,
        /// <summary>
        /// 弹出系统拍照发图
        /// </summary>
        pic_sysphoto = 5,
        /// <summary>
        /// 弹出拍照或者相册发图
        /// </summary>
        pic_photo_or_album = 6,
        /// <summary>
        /// 弹出微信相册发图器
        /// </summary>
        pic_weixin = 7,
        /// <summary>
        /// 弹出地理位置选择器
        /// </summary>
        location_select = 8,
        /// <summary>
        /// 下发消息(除文本消息)
        /// </summary>
        media_id = 9,
        /// <summary>
        /// 跳转图文消息URL
        /// </summary>
        view_limited = 10
    }
3)定义菜单基类:
    /// <summary>
    /// 菜单按钮基类
    /// </summary>
    public class MenuButtonBase
    {
        /// <summary>
        /// 菜单标题,不超过16个字节,子菜单不超过40个字节
        /// </summary>
        [MaxLength(20)]
        [JsonProperty(PropertyName = "name", Required = Required.Always)]
        public virtual string Name { get; set; }
        /// <summary>
        /// 菜单类型(菜单的响应动作类型)
        /// </summary>
        [JsonConverter(typeof(StringEnumConverter))]
        [JsonProperty(PropertyName = "type")]
        public MenuButtonTypes Type { get; set; }
    }

注意:
JsonProperty:用于指定属性
JsonConverter:用于设置转换器,这里使用了StringEnumConverter,用于将字符串转换为相应的枚举类型。
MaxLength 目前没有意义

4)定义按钮:
    /// <summary>
    /// 子菜单按钮
    /// </summary>
    public class SubMenuButton : MenuButtonBase
    {
        /// <summary>
        /// 菜单标题,不超过16个字节,子菜单不超过40个字节
        /// </summary>
        [MaxLength(8)]
        [JsonProperty(PropertyName = "name", Required = Required.Always)]
        public override string Name { get; set; }
        /// <summary>
        /// 子菜单(二级菜单数组,个数应为1~5个)
        /// </summary>
        [JsonProperty(PropertyName = "sub_button")]
        public List<MenuButtonBase> SubButtons { get; set; }
   }
   /// <summary>
   /// Click按钮(点击推事件)
   /// 用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
   /// </summary>
   public class ClickButton : MenuButtonBase
   {
       public ClickButton()
       {
           this.Type = MenuButtonTypes.click;
       }
       /// <summary>
       /// 菜单KEY值,用于消息接口推送,不超过128字节
       /// </summary>
       [JsonProperty(PropertyName = "key", Required = Required.Always)]
       public string Key { get; set; }
   }
   /// <summary>
   /// 下发消息(除文本消息)    
   /// 用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
   /// </summary>
   public class MediaIdButton : MenuButtonBase
   {
       public MediaIdButton()
       {
           this.Type = MenuButtonTypes.media_id;
       }
       /// <summary>
       /// 调用新增永久素材接口返回的合法media_id
       /// </summary>
       [JsonProperty(PropertyName = "media_id", Required = Required.Always)]
       public string MediaId { get; set; }
   }
   /// 其他按钮封装省略
5)定义自定义对象创建转换器(CustomCreationConverter):
图2-1自定义对象转换器源码分析

从源码中我们可以看出,Create方法必须实现,除此之外,因为我们的目的是为了解析微信返回的消息,只需要实现ReadJson方法即可(当然实现CanConvert方法能让代码更严谨)。

    /// <summary>
    /// 菜单按钮自定义对象创建转换器
    /// 根据菜单类型自定义创建
    /// </summary>
    public class MenuButtonsCustomConverter : CustomCreationConverter<MenuButtonBase>
    {
        /// <summary>
        /// 读取目标对象的JSON表示
        /// </summary>
        /// <param name="reader">JsonReader</param>
        /// <param name="objectType">对象类型</param>
        /// <param name="existingValue"></param>
        /// <param name="serializer"></param>
        /// <returns>对象</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return null;
            var jObject = JObject.Load(reader);
            MenuButtonBase target = default(MenuButtonBase);
            //获取type属性
            var type = jObject.Property("type");
            if (type != null && type.Count > 0)
            {
                var typeValue = type.Value.ToString();
                var menuButtonType = (MenuButtonTypes)Enum.Parse(typeof(MenuButtonTypes), typeValue);
                #region 根据类型返回相应菜单类型
                switch (menuButtonType)
                {
                    case MenuButtonTypes.click:
                        target = new ClickButton();
                        break;
                    case MenuButtonTypes.view:
                        target = new ViewButton();
                        break;
                    case MenuButtonTypes.scancode_push:
                        target = new ScancodePushButton();
                        break;
                    case MenuButtonTypes.scancode_waitmsg:
                        target = new ScancodeWaitmsgButton();
                        break;
                    case MenuButtonTypes.pic_sysphoto:
                        target = new PicSysphotoButton();
                        break;
                    case MenuButtonTypes.pic_photo_or_album:
                        target = new PicPhotoOrAlbumButton();
                        break;
                    case MenuButtonTypes.pic_weixin:
                        target = new PicWeixinButton();
                        break;
                    case MenuButtonTypes.location_select:
                        target = new LocationSelectButton();
                        break;
                    case MenuButtonTypes.media_id:
                        target = new MediaIdButton();
                        break;
                    case MenuButtonTypes.view_limited:
                        target = new ViewLimitedButton();
                        break;
                    default:
                        throw new NotSupportedException("不支持此类型的菜单按钮:" + menuButtonType);
                } 
                #endregion
            }
            else
            {
                target = new SubMenuButton();
            }
            serializer.Populate(jObject.CreateReader(), target);
            return target;
        }
        /// <summary>
        /// 创建对象(会被填充)
        /// </summary>
        /// <param name="objectType">对象类型</param>
        /// <returns>对象</returns>
        public override MenuButtonBase Create(Type objectType)
        {
            return new SubMenuButton();
        }
    }
5)测试结果:
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容