Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON Stringifier #22394

Open
hydrati opened this issue Jan 19, 2023 · 2 comments · May be fixed by #22395
Open

JSON Stringifier #22394

hydrati opened this issue Jan 19, 2023 · 2 comments · May be fixed by #22395
Labels
new-challenge Propose a new challenge, a PR will be auto generated

Comments

@hydrati
Copy link
Contributor

hydrati commented Jan 19, 2023

Please follow the template and fill the info. A PR will be auto-generated and always reflect on your changes.

Detailed solution/guide is not required, but please be sure the challenge is solvable.

Info

Basic info of your challenge questions,

difficulty: extreme # medium / hard / extreme
title: JSON Stringifier
tags: json, union, template-literal # separate by comma

Question

You're required to implement a type-level partly stringifier to stringify a literal type into a JSON string.

Requirements:

  • Numbers (number, bigint), Symbol (symbol), Unicode escape (\uxxxx), undefined in the input can be ignored. You needn't to stringify them.

Template

This is the template for challengers to start the coding. Basically, you just need to change the name of your generic/function and leave to implementation any.

type Stringify<T extends unknown> = any

Test Cases

Provide some test cases for your challenge, you can use some utils from @type-challenges/utils for asserting.

import type { Equal, Expect } from '@type-challenges/utils'
;(() => {
  type Cases = [
    Expect<ParseEqual<{
      "a": "b",
      "b": false,
      "c": [true, false, "hello", {
        "a": "b",
        "b": false
      }],
      "nil": null
    }>>,
    Expect<ParseEqual<{}>>,
    Expect<ParseEqual<[]>>,
    Expect<ParseEqual<[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]>>,
    Expect<Equal<Stringify<[1]>, never>>,
    Expect<ParseEqual<true>>,
    Expect<ParseEqual<false>>,
    Expect<ParseEqual<null>>,
    Expect<ParseEqual<['Hello', true, false, null]>>,
    Expect<ParseEqual<{
      'hello\r\n\b\f': 'world',
      'world': null
    }>>,
    Expect<Equal<Stringify<{ 1: "world" }>, never>>,
    Expect<Equal<Stringify<1>, never>>,
    Expect<Equal<Stringify<symbol>, never>>,
    Expect<Equal<Stringify<string>, never>>,
    Expect<Equal<Stringify<boolean>, never>>,
    Expect<Equal<Stringify<unknown[]>, never>>,
    Expect<Equal<Stringify<Record<string, unknown>>, never>>,
    Expect<Equal<Stringify<any>, never>>,
    Expect<Equal<Stringify<unknown>, never>>,
    Expect<Equal<Stringify<never>, never>>,
    Expect<ParseEqual<{
      "hello\"": "world\n you're in\b\f\n\r\t\b\f\n\r\t",
      sites: [ "Google", "Bing"],
      true: ["or", false],
      false: [true],
      a: {
        b: {
          c: {
            d: [{
              ok: false
            }]
          }
        }
      }
    }>>,
    Expect<Equal<Stringify<`"'"'\\/\b\f\n\r\t `>, "\"\\\"'\\\"'\\\\\\/\\b\\f\\n\\r\\t \"">>
  ]


  type ParseEqual<T> = Equal<Parse<Stringify<T>>, Merge<T>>

  // JSON Parser (#6228), see https://tsch.js.org/6228
  // Don't cheat. :)
  type Escape = {
    '"': '"',
    '\\': '\\',
    '/': '/',
    'b': '\b',
    'f': '\f',
    'n': '\n',
    'r': '\r',
    't': '\t'
  }

  type Space = ' ' | '\r' | '\t' | '\n'
  type Symbols = '{' | '}' | '[' | ']' | ':' | ','
  type SymbolToken<T extends Symbols> = [0, T]
  type StringToken<T extends string> = [1, T]
  type BooleanToken<T extends boolean> = [2, T]
  type NullToken = [3, null]
  type Token =
    | SymbolToken<Symbols>
    | StringToken<string>
    | BooleanToken<boolean>
    | NullToken
    | [number, unknown]

  type EvalResult<Output, Tokens extends Token[]> = [Output, Tokens]
  type TokenSplit<First extends Token, Rest extends Token[]> = [First, ...Rest]
  type Tokenize<Input extends string, Output extends Token[] = []> =
    Input extends `${infer First}${infer Rest}`
    ? First extends Space
      ? Tokenize<Rest, Output>
    : First extends Symbols
      ? Tokenize<Rest, [...Output, SymbolToken<First>]>
    : First extends '"'
      ? [TakeString<Rest>] extends [never]
        ? never
        : [...Output, ...TakeString<Rest>]
    : Input extends `true${infer Rest}`
      ? Tokenize<Rest, [...Output, BooleanToken<true>]>
    : Input extends `false${infer Rest}`
      ? Tokenize<Rest, [...Output, BooleanToken<false>]>
    : Input extends `null${infer Rest}`
      ? Tokenize<Rest, [...Output, NullToken]>
      : never
    : Input extends ''
      ? Output
      : never

  type TakeString<Input extends string, Output extends string = ''> =
    Input extends `\\${infer Rest}`
    ? Rest extends `${infer First extends keyof Escape}${infer Rest}`
      ? TakeString<Rest, `${Output}${Escape[First]}`>
      : never
    : Input extends `${infer First}${infer Rest}`
      ? First extends '\n'
        ? never
        : First extends '"'
          ? [StringToken<Output>, ...Tokenize<Rest>]
          : TakeString<Rest, `${Output}${First}`>
      : never


  type EvalPrimitive<Input extends Token[]> =
    Input extends TokenSplit<[1 | 2 | 3, infer Output], infer Rest>
    ? EvalResult<Output, Rest>
    : never

  type EvalArrayElements<Input extends Token[], Output extends unknown[] = []> =
    Input extends TokenSplit<infer First, infer Rest>
    ? First extends SymbolToken<']'>
      ? EvalResult<Output, Rest>
      : Eval<Input> extends EvalResult<infer Element, infer Rest>
        ? Rest extends TokenSplit<infer First, infer Rest>
          ? First extends SymbolToken<']'>
            ? EvalResult<[...Output, Element], Rest>
            : First extends SymbolToken<','>
              ? EvalArrayElements<Rest, [...Output, Element]>
              : never
          : never
        : never
    : never

  type EvalArray<Input extends Token[]> =
    Input extends TokenSplit<SymbolToken<'['>, infer Rest>
    ? EvalArrayElements<Rest>
    : never

  type EvalObjectEntries<Input extends Token[], Output extends Record<string, unknown> = {}> =
    Input extends TokenSplit<infer First, infer Rest>
    ? First extends SymbolToken<'}'>
      ? EvalResult<Output, Rest>
      : Eval<Input> extends EvalResult<infer Key extends string, infer Rest>
        ? Rest extends TokenSplit<SymbolToken<':'>, infer Rest>
          ? Eval<Rest> extends EvalResult<infer Value, infer Rest>
            ? Output & Record<Key, Value> extends infer Output extends Record<string, unknown>
              ? Rest extends TokenSplit<infer First, infer Rest>
                ? First extends SymbolToken<'}'>
                  ? EvalResult<Output, Rest>
                  : First extends SymbolToken<','>
                    ? EvalObjectEntries<Rest, Output>
                    : never
                : never
              : never
            : never
          : never
        : never
    : never

  type EvalObject<Input extends Token[]> =
    Input extends TokenSplit<SymbolToken<'{'>, infer Rest>
    ? EvalObjectEntries<Rest>
    : never

  type Eval<Input extends Token[]> =
    | EvalPrimitive<Input>
    | EvalArray<Input>
    | EvalObject<Input>

  type Merge<T> =
    T extends object
    ? { [P in keyof T]: Merge<T[P]> }
    : T

  type Parse<T extends string> =
    string extends T
    ? never
    : [Tokenize<T>] extends [never]
      ? never
      : Eval<Tokenize<T>> extends EvalResult<infer Output, []>
        ? Merge<Output>
        : never
})()
@hydrati hydrati added the new-challenge Propose a new challenge, a PR will be auto generated label Jan 19, 2023
@github-actions github-actions bot linked a pull request Jan 19, 2023 that will close this issue
@github-actions
Copy link
Contributor

github-actions bot commented Jan 19, 2023

#22395 - Pull Request updated.

2023-01-19T15:27:11.388Z Preview in Playground

@hydrati
Copy link
Contributor Author

hydrati commented Jan 19, 2023

Possible solution:

/* Imported from '@type-challenges/utils' */
// type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false

type Escape = {
  "\"": '\\"',
  "\\": '\\\\',
  "\/": '\\/',
  '\b': '\\b',
  '\f': '\\f',
  '\n': '\\n',
  '\r': '\\r',
  '\t': '\\t'
}

type StringifyPrimitive<T> =
  Equal<T, true> extends true
    ? 'true'
  : Equal<T, false> extends true
    ? 'false'
  : Equal<T, null> extends true
    ? `null`
  : T extends string
    ? [EscapeString<T>] extends [never]
      ? never
      : `"${EscapeString<T>}"`
    : never

type EscapeString<T extends string, Output extends string = ''> =
  string extends T
  ? never
  : T extends ''
    ? Output
    : T extends `${infer P}${infer R}`
      ? EscapeString<R, `${Output}${P extends keyof Escape ? Escape[P] : P}`>
      : never

type StringifyArrayElement<T extends unknown[], Output extends string = ''> =
  T extends [infer P, ...infer R]
  ? StringifyArrayElement<R, `${Output}${Output extends '' ? '' : ','}${Stringify<P>}`>
  : T extends []
    ? Output
    : never

type StringifyArray<T extends unknown> =
  T extends unknown[]
  ? number extends T['length']
    ? never
    : `[${StringifyArrayElement<T>}]`
  : never

type StringifyObject<T extends unknown> =
  T extends Record<string, unknown>
  ? Record<string, unknown> extends T
    ? Equal<T, {}> extends true
      ? `{}`
      : never
    : `{${StringifyObjectEntries<T>}}`
  : never

type UnionIntersectionFn<T> =
  (T extends T ? (x: () => T) => never : never) extends ((x: infer P) => never)
  ? P
  : never

type UnionLast<T> = UnionIntersectionFn<T> extends () => infer P ? P : never

type StringifyObjectEntries<T extends Record<string, unknown>, Output extends string = ''> =
  [keyof T] extends [never]
  ? Output
  : UnionLast<keyof T> extends infer Key extends string
    ? StringifyObjectEntries<{ [P in Exclude<keyof T, Key>]: T[P] }, `${Output}${Output extends '' ? '' : ','}${StringifyPrimitive<Key>}:${Stringify<T[Key]>}`>
    : never

type Stringify<T extends unknown> =
  | StringifyArray<T>
  | StringifyPrimitive<T>
  | StringifyObject<T>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new-challenge Propose a new challenge, a PR will be auto generated
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant