用C#实现一个Json解析器(2)——详细设计①

前言

上回我们列出了解析器的全部功能并逐条进行了分析。这次我们以上次的分析结果作为依据设计出整个系统的框架。

类提取

类和对象是OO系统的一等公民。我们在设计OO系统时,肯定要先做类提取。下面就分几步进行这个过程,最后设计出系统的完整类图。

必要的类

  1. Lexer(词法分析器):将Json字符串转换成C#对象,本质是一个翻译的过程。首先,需要整个Json字符串拆分成一个一个的单词(Token),为下一步的分析提供方便。词法分析器的职责就是将输入的Json字符串转换成单词序列。
  2. Token(单词):词法分析器拆分出的单词可并不仅仅是一个字符串,它还需要包含这个单词的词性(TokenType),于是我们用一个类来封装这两条属性。
  3. TokenType(词性):这是一个枚举类,表示Json字符串中可以出现的词性的集合。为了方便语法分析阶段的语法校验,这个类的成员应该被设计成位域。包括:对象起始符(ObjectStart),对象终止符(ObjectEnd),数组起始符(ArrayStart),数组终止符(ArrayEnd),冒号(Colon),逗号(Comma),数字(Number),字符串(String),字面量(Constant)和终止符(End)。
  4. Parser(语法分析器):语法分析是解析过程的第二部,语法分析器接收词法分析器输出的单词序列,将其转换为抽象语法树…其实不用这么大费周章,Json的语法非常简单,这一步我们直接分析单词序列就可以得到最后的C#对象。因此,生成抽象语法树以及后面的语义分析过程完全可以省略,来提高程序的执行效率。
  5. JsonBaseType(Json基本类型):这是一个枚举类,表示Json字符串中可以出现的基本数据类型。由于Json和C#的基本数据类型之间并非双射(一一映射),所以这个类是有其存在的必要性的。包括:数字(Number),字符串(String),空值(Null),布尔值(Boolean)。
  6. JsonMapper(映射器):上面的五个类均被标记为internal,用户无法直接访问。这个类作为系统的边界和控制器,用于包装整个系统,屏蔽底层的复杂性,负责接收用户的请求并驱动底层模块实现功能。
  7. KeyAttribute(键名):这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器将该成员与给定的Json字符串键值对对应。
  8. ConvertAllAttribute(全部转换):这是一个特性类,可以标记类或结构体,不继承,禁止多重标记,它提示解析器解析该类或结构体的所有成员。
  9. IgnoreAllAttribute(全部忽略):这是一个特性类,可以标记类或结构体,不继承,禁止多重标记,它提示解析器忽略该类或结构体的所有成员。
  10. ConvertAttribute(转换): 这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器解析该成员。
  11. IgnoreAttribute(忽略):这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器忽略该成员。
  12. ObjectConverterAttribute(对象转换器):这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器使用特定的转换器进行Json键值对到对象成员的转换。
  13. JsonConverterAttribute(Json转换器):这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器使用特定的转换器进行对象成员到Json键值对的转换。
  14. NotNullAttribute(非空):这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器若该成员为null则抛出异常。
  15. RangeAttribute(值域):这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器若该成员无法与实数进行比较或超出了值域则抛出异常。
  16. LengthAttribute(长度):这是一个特性类,可以标记字段或属性,可继承,禁止多重标记,它提示解析器若满足以下任意一条则抛出异常:①该成员不是String、IList、IList<T>中的任何一个。②该成员是String且Length属性超出了值域。③该成员是IList或IList<T>且Count属性超出了值域。
  17. ObjectConverter(对象转换函数):这是一个委托类,返回值为Object,参数列表为String,表示从Json键值对到对象成员的转换函数。
  18. JsonConverter(Json转换函数):这是一个委托类,返回值为String,参数列表为Object,表示从对象成员到Json键值对的转换函数。

扩展UML类图

就目前的情况来看,我们使用了很多C#的高级特性(委托、反射),导致一些语义用标准UML类图无法表达。因此,我们需要添加一些构造型:

  1. <<attributeXXX>>构造型用来标记一个特性类,后三个字符表示这个特性的用法:第一个字符表示有效目标,C(Class)表示类或结构体,M(Member)表示字段或属性;后两个字符分别表示是否继承和是否允许多重标记,T表示是,F表示否。
  2. <<delegate>>构造型用来标记一个委托类,委托类没有属性,只有一个Invoke方法。
  3. <<enum>>构造型用来标记一个枚举类,枚举类没有方法,只有一些属性,但与普通类的写法不同,冒号后的类型改为值。
  4. <<flags>>构造型继承于<<enum>>构造型,它表示这个枚举类的成员是位域。
  5. <<static>>构造型用来标记一个静态类。

下面是几个示例:
用C#实现一个Json解析器(2)——详细设计①

第一阶段成果

有了这些扩展,我们就能根据上面的分析得到一个大体的结构了:
用C#实现一个Json解析器(2)——详细设计①
到目前为止,我们得到了系统最最原始的模型,这个模型已经可以实现所有功能了。但是,一个好的程序猿是不可能满足于实现功能的。下回,我们来真正地“设计”一下这个系统,让它在高效完成任务的同时具备易用性和灵活性。